merged radius work with latest mainline tip

This commit is contained in:
John Dennis
2007-11-28 07:49:07 -05:00
100 changed files with 5561 additions and 1025 deletions

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"
@@ -46,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
@@ -69,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(14, "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()
@@ -94,9 +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()
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")
@@ -104,18 +170,10 @@ 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,
PASSWORD=self.dm_password, SUFFIX=self.suffix,
PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(),
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root)
@@ -161,11 +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",
schema_dirname(self.realm_name) + "60ipaconfig.ldif")
def __add_memberof_module(self):
self.step("enabling memboerof plugin")
@@ -177,6 +237,16 @@ class DsInstance(service.Service):
logging.critical("Failed to load memberof-conf.ldif: %s" % str(e))
memberof_fd.close()
def __init_memberof(self):
self.step("initializing group membership")
memberof_txt = template_file(SHARE_DIR + "memberof-task.ldif", self.sub_dict)
memberof_fd = write_tmp_file(memberof_txt)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except subprocess.CalledProcessError, e:
logging.critical("Failed to load memberof-conf.ldif: %s" % str(e))
memberof_fd.close()
def __add_referint_module(self):
self.step("enabling referential integrity plugin")
referint_txt = template_file(SHARE_DIR + "referint-conf.ldif", self.sub_dict)
@@ -219,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:
@@ -257,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)
@@ -265,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

@@ -50,13 +50,17 @@ def update_file(filename, orig, subst):
else:
sys.stdout.write(p.sub(subst, line))
fileinput.close()
return 0
else:
print "File %s doesn't exist." % filename
return 1
class HTTPInstance(service.Service):
def __init__(self):
service.Service.__init__(self, "httpd")
def create_instance(self, realm, fqdn):
self.sub_dict = { "REALM" : realm }
self.sub_dict = { "REALM" : realm, "FQDN": fqdn }
self.fqdn = fqdn
self.realm = realm
@@ -137,4 +141,5 @@ class HTTPInstance(service.Service):
def __set_mod_nss_port(self):
self.step("Setting mod_nss port to 443")
update_file(NSS_CONF, '8443', '443')
if update_file(NSS_CONF, '8443', '443') != 0:
print "Updating %s failed." % NSS_CONF

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)
@@ -377,6 +449,23 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
return "Success"
def updateRDN(self, dn, newrdn):
"""Wrap the modrdn function."""
sctrl = self.__get_server_controls__()
if dn == newrdn:
# no need to report an error
return "Success"
try:
if sctrl is not None:
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modrdn_s(dn, newrdn, delold=1)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
return "Success"
def updateEntry(self,dn,olduser,newuser):
"""This wraps the mod function. It assumes that the entry is already
populated with all of the desired objectclasses and attributes"""
@@ -521,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 = []
@@ -543,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
@@ -557,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

@@ -26,29 +26,32 @@ import logging
import fileinput
import re
import sys
from random import Random
from time import gmtime
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, namedtype
import pyasn1.codec.ber.encoder
import pyasn1.codec.ber.decoder
import struct
import base64
def host_to_domain(fqdn):
s = fqdn.split(".")
return ".".join(s[1:])
def generate_kdc_password():
rndpwd = ''
r = Random()
r.seed(gmtime())
for x in range(12):
# rndpwd += chr(r.randint(32,126))
rndpwd += chr(r.randint(65,90)) #stricter set for testing
return rndpwd
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)
@@ -79,18 +82,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.kdc_password = ipa_generate_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()
@@ -98,22 +109,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()
@@ -129,8 +125,49 @@ 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")
os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600)
def __configure_kdc_account_password(self):
self.step("setting KDC account password")
hexpwd = ''
@@ -139,6 +176,7 @@ class KrbInstance(service.Service):
pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w")
pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n")
pwd_fd.close()
os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600)
def __setup_sub_dict(self):
self.sub_dict = dict(FQDN=self.fqdn,
@@ -149,9 +187,60 @@ class KrbInstance(service.Service):
HOST=self.host,
REALM=self.realm)
def __configure_ldap(self):
self.step("adding kerberos configuration to the directory")
#TODO: test that the ldif is ok with any random charcter we may use in the password
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:
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:
@@ -169,7 +258,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+")
@@ -197,12 +286,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):
@@ -215,12 +326,31 @@ class KrbInstance(service.Service):
logging.critical("Failed to load pwd-extop-conf.ldif: %s" % str(e))
extop_fd.close()
#add an ACL to let the DS user read the master key
args = ["/usr/bin/setfacl", "-m", "u:"+self.ds_user+":r", "/var/kerberos/krb5kdc/.k5."+self.realm]
#get the Master Key from the stash file
try:
run(args)
except subprocess.CalledProcessError, e:
logging.critical("Failed to set the ACL on the master key: %s" % str(e))
stash = open("/var/kerberos/krb5kdc/.k5."+self.realm, "r")
keytype = struct.unpack('h', stash.read(2))[0]
keylen = struct.unpack('i', stash.read(4))[0]
keydata = stash.read(keylen)
except os.error:
logging.critical("Failed to retrieve Master Key from Stash file: %s")
#encode it in the asn.1 attribute
MasterKey = univ.Sequence()
MasterKey.setComponentByPosition(0, univ.Integer(keytype))
MasterKey.setComponentByPosition(1, univ.OctetString(keydata))
krbMKey = univ.Sequence()
krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno
krbMKey.setComponentByPosition(1, MasterKey)
asn1key = pyasn1.codec.ber.encoder.encode(krbMKey)
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:
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

@@ -26,6 +26,7 @@ import shutil
import logging
import pwd
import time
import sys
from ipa.ipautil import *
from ipa import radius_util
@@ -147,8 +148,7 @@ class RadiusInstance(service.Service):
retry += 1
if retry > 15:
print "Error timed out waiting for kadmin to finish operations\n"
sys.exit()
sys.exit(1)
try:
pent = pwd.getpwnam(radius_util.RADIUS_USER)
os.chown(radius_util.RADIUS_IPA_KEYTAB_FILEPATH, pent.pw_uid, pent.pw_gid)

View File

@@ -0,0 +1,316 @@
# 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 time, logging
import ipaldap, ldap, dsinstance
from ipa import ipaerror
DIRMAN_CN = "cn=directory manager"
PORT = 389
TIMEOUT = 120
class ReplicationManager:
"""Manage replicatin agreements between DS servers"""
def __init__(self, hostname, dirman_passwd):
self.hostname = hostname
self.dirman_passwd = dirman_passwd
self.conn = ipaldap.IPAdmin(hostname)
self.conn.do_simple_bind(bindpw=dirman_passwd)
self.repl_man_passwd = dirman_passwd
# these are likely constant, but you could change them
# at runtime if you really want
self.repl_man_dn = "cn=replication manager,cn=config"
self.repl_man_cn = "replication manager"
self.suffix = ""
def find_replication_dns(self, conn):
filt = "(objectlcass=nsds5ReplicationAgreement)"
try:
ents = conn.search_s("cn=mapping tree,cn-config", ldap.SCOPE_SUBTREE, filt, ["cn"])
except ldap.NO_SUCH_OBJECT:
return []
return [ent.dn for ent in ents]
def add_replication_manager(self, conn, passwd=None):
"""
Create a pseudo user to use for replication. If no password
is provided the directory manager password will be used.
"""
if passwd:
self.repl_man_passwd = passwd
ent = ipaldap.Entry(self.repl_man_dn)
ent.setValues("objectclass", "top", "person")
ent.setValues("cn", self.repl_man_cn)
ent.setValues("userpassword", self.repl_man_passwd)
ent.setValues("sn", "replication manager pseudo user")
try:
conn.add_s(ent)
except ldap.ALREADY_EXISTS:
# should we set the password here?
pass
def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"):
try:
conn.delete_s(dn)
except ldap.NO_SUCH_OBJECT:
pass
def get_replica_type(self, master):
if master:
return "3"
else:
return "2"
def replica_dn(self):
return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix
def local_replica_config(self, conn, master, replica_id):
dn = self.replica_dn()
try:
conn.getEntry(dn, ldap.SCOPE_BASE)
# replication is already configured
return
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
pass
replica_type = self.get_replica_type(master)
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject")
entry.setValues('cn', "replica")
entry.setValues('nsds5replicaroot', self.suffix)
entry.setValues('nsds5replicaid', str(replica_id))
entry.setValues('nsds5replicatype', replica_type)
entry.setValues('nsds5flags', "1")
entry.setValues('nsds5replicabinddn', [self.repl_man_dn])
entry.setValues('nsds5replicalegacyconsumer', "off")
conn.add_s(entry)
def setup_changelog(self, conn):
dn = "cn=changelog5, cn=config"
dirpath = conn.dbdir + "/cldb"
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "extensibleobject")
entry.setValues('cn', "changelog5")
entry.setValues('nsslapd-changelogdir', dirpath)
try:
conn.add_s(entry)
except ldap.ALREADY_EXISTS:
return
def setup_chaining_backend(self, conn):
chaindn = "cn=chaining database, cn=plugins, cn=config"
benamebase = "chaindb"
urls = [self.to_ldap_url(conn)]
cn = ""
benum = 1
done = False
while not done:
try:
cn = benamebase + str(benum) # e.g. localdb1
dn = "cn=" + cn + ", " + chaindn
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance')
entry.setValues('cn', cn)
entry.setValues('nsslapd-suffix', self.suffix)
entry.setValues('nsfarmserverurl', urls)
entry.setValues('nsmultiplexorbinddn', self.repl_man_dn)
entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd)
self.conn.add_s(entry)
done = True
except ldap.ALREADY_EXISTS:
benum += 1
except ldap.LDAPError, e:
print "Could not add backend entry " + dn, e
raise
return cn
def to_ldap_url(self, conn):
return "ldap://%s:%d/" % (conn.host, conn.port)
def setup_chaining_farm(self, conn):
try:
conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci',
[ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])])
except ldap.TYPE_OR_VALUE_EXISTS:
logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host))
def get_mapping_tree_entry(self):
try:
entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL,
"(cn=\"%s\")" % (self.suffix))
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e:
logging.debug("failed to find mappting tree entry for %s" % self.suffix)
raise e
return entry
def enable_chain_on_update(self, bename):
mtent = self.get_mapping_tree_entry()
dn = mtent.dn
plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config",
ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath'])
path = plgent.getValue('nsslapd-pluginPath')
mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'),
(ldap.MOD_ADD, 'nsslapd-backend', bename),
(ldap.MOD_ADD, 'nsslapd-distribution-plugin', path),
(ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')]
try:
self.conn.modify_s(dn, mod)
except ldap.TYPE_OR_VALUE_EXISTS:
logging.debug("chainOnUpdate already enabled for %s" % self.suffix)
def setup_chain_on_update(self, other_conn):
chainbe = self.setup_chaining_backend(other_conn)
self.enable_chain_on_update(chainbe)
def agreement_dn(self, conn):
cn = "meTo%s%d" % (conn.host, PORT)
dn = "cn=%s, %s" % (cn, self.replica_dn())
return (cn, dn)
def setup_agreement(self, a, b):
cn, dn = self.agreement_dn(b)
try:
a.getEntry(dn, ldap.SCOPE_BASE)
return
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
pass
entry = ipaldap.Entry(dn)
entry.setValues('objectclass', "top", "nsds5replicationagreement")
entry.setValues('cn', cn)
entry.setValues('nsds5replicahost', b.host)
entry.setValues('nsds5replicaport', str(PORT))
entry.setValues('nsds5replicatimeout', str(TIMEOUT))
entry.setValues('nsds5replicabinddn', self.repl_man_dn)
entry.setValues('nsds5replicacredentials', self.repl_man_passwd)
entry.setValues('nsds5replicabindmethod', 'simple')
entry.setValues('nsds5replicaroot', self.suffix)
entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456')
entry.setValues('description', "me to %s%d" % (b.host, PORT))
a.add_s(entry)
entry = a.waitForEntry(entry)
def check_repl_init(self, conn, agmtdn):
done = False
hasError = 0
attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5replicaUpdateInProgress',
'nsds5ReplicaLastInitStatus', 'nsds5ReplicaLastInitStart',
'nsds5ReplicaLastInitEnd']
entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist)
if not entry:
print "Error reading status from agreement", agmtdn
hasError = 1
else:
refresh = entry.nsds5BeginReplicaRefresh
inprogress = entry.nsds5replicaUpdateInProgress
status = entry.nsds5ReplicaLastInitStatus
if not refresh: # done - check status
if not status:
print "No status yet"
elif status.find("replica busy") > -1:
print "Update failed - replica busy - status", status
done = True
hasError = 2
elif status.find("Total update succeeded") > -1:
print "Update succeeded"
done = True
elif inprogress.lower() == 'true':
print "Update in progress yet not in progress"
else:
print "Update failed: status", status
hasError = 1
done = True
else:
print "Update in progress"
return done, hasError
def wait_for_repl_init(self, conn, agmtdn):
done = False
haserror = 0
while not done and not haserror:
time.sleep(1) # give it a few seconds to get going
done, haserror = self.check_repl_init(conn, agmtdn)
return haserror
def start_replication(self, other_conn):
print "starting replication"
cn, dn = self.agreement_dn(self.conn)
mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')]
other_conn.modify_s(dn, mod)
return self.wait_for_repl_init(other_conn, dn)
def basic_replication_setup(self, conn, master, replica_id):
self.add_replication_manager(conn)
self.local_replica_config(conn, master, replica_id)
if master:
self.setup_changelog(conn)
def setup_replication(self, other_hostname, realm_name, master=True):
"""
NOTES:
- the directory manager password needs to be the same on
both directories.
"""
other_conn = ipaldap.IPAdmin(other_hostname)
other_conn.do_simple_bind(bindpw=self.dirman_passwd)
self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name))
self.basic_replication_setup(self.conn, master, 1)
self.basic_replication_setup(other_conn, True, 2)
self.setup_agreement(other_conn, self.conn)
if master:
self.setup_agreement(self.conn, other_conn)
else:
self.setup_chaining_farm(other_conn)
self.setup_chain_on_update(other_conn)
return self.start_replication(other_conn)