diff --git a/Makefile b/Makefile index 350bf9bc2..5de0c5d3b 100644 --- a/Makefile +++ b/Makefile @@ -53,10 +53,20 @@ version-update: > ipa-python/freeipa-python.spec -tarballs: +archive: -mkdir -p dist hg archive -t files dist/freeipa +local-archive: + -mkdir -p dist/freeipa + @for subdir in $(SUBDIRS); do \ + cp -pr $$subdir dist/freeipa/.; \ + done + +archive-cleanup: + rm -fr dist/freeipa + +tarballs: # ipa-server mv dist/freeipa/ipa-server dist/$(SERV_TARBALL_PREFIX) rm -f dist/$(SERV_TARBALL) @@ -75,9 +85,6 @@ tarballs: cd dist; tar cfz $(PYTHON_TARBALL) $(PYTHON_TARBALL_PREFIX) rm -fr dist/$(PYTHON_TARBALL_PREFIX) - # cleanup - rm -fr dist/freeipa - rpm-ipa-server: cp dist/$(SERV_TARBALL) ~/rpmbuild/SOURCES/. rpmbuild -ba ipa-server/freeipa-server.spec @@ -98,7 +105,9 @@ rpm-ipa-python: rpms: rpm-ipa-server rpm-ipa-admin rpm-ipa-python -dist: version-update tarballs rpms +dist: version-update archive tarballs archive-cleanup rpms + +local-dist: clean version-update local-archive tarballs archive-cleanup rpms dist-clean: clean rm -fr dist diff --git a/ipa-admintools/freeipa-admintools.spec b/ipa-admintools/freeipa-admintools.spec index bcd3d9d29..904a3b693 100755 --- a/ipa-admintools/freeipa-admintools.spec +++ b/ipa-admintools/freeipa-admintools.spec @@ -1,6 +1,6 @@ Name: freeipa-admintools Version: 0.1.0 -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -36,7 +36,12 @@ rm -rf %{buildroot} %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Update tools to do kerberos +- Add User class + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version - - diff --git a/ipa-admintools/freeipa-admintools.spec.in b/ipa-admintools/freeipa-admintools.spec.in index bcd3d9d29..904a3b693 100755 --- a/ipa-admintools/freeipa-admintools.spec.in +++ b/ipa-admintools/freeipa-admintools.spec.in @@ -1,6 +1,6 @@ Name: freeipa-admintools Version: 0.1.0 -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -36,7 +36,12 @@ rm -rf %{buildroot} %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Update tools to do kerberos +- Add User class + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version - - diff --git a/ipa-admintools/ipa-adduser b/ipa-admintools/ipa-adduser index b40fdee2c..af922833d 100644 --- a/ipa-admintools/ipa-adduser +++ b/ipa-admintools/ipa-adduser @@ -21,8 +21,11 @@ import sys from optparse import OptionParser import ipa -import ipa.rpcclient +import ipa.ipaclient as ipaclient +import ipa.config + import xmlrpclib +import kerberos def usage(): print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user" @@ -43,7 +46,8 @@ def parse_options(): parser.add_option("--usage", action="store_true", help="Program usage") - (options, args) = parser.parse_args() + args = ipa.config.init_config(sys.argv) + options, args = parser.parse_args(args) if not options.gn or not options.sn: usage() @@ -52,14 +56,14 @@ def parse_options(): def main(): user={} - (options, args) = parse_options() + options, args = parse_options() - if len(args) != 1: + if len(args) != 2: usage() - user['gn'] = options.gn + user['givenName'] = options.gn user['sn'] = options.sn - user['uid'] = args[0] + user['uid'] = args[1] if options.gecos: user['gecos'] = options.gecos if options.directory: @@ -70,10 +74,15 @@ def main(): user['loginshell'] = "/bin/bash" try: - ipa.rpcclient.add_user(user) - print args[0] + " successfully added" + client = ipaclient.IPAClient() + client.add_user(user) + print args[1] + " successfully added" except xmlrpclib.Fault, f: print f.faultString + return 1 + except kerberos.GSSError, e: + print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) + return 1 return 0 diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser index 205b47ce9..a54e141e7 100644 --- a/ipa-admintools/ipa-finduser +++ b/ipa-admintools/ipa-finduser @@ -18,12 +18,14 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +import sys from optparse import OptionParser -import ipa -import ipa.rpcclient -import base64 +import ipa.ipaclient as ipaclient +import ipa.config + import sys import xmlrpclib +import kerberos def usage(): print "ipa-finduser " @@ -32,28 +34,40 @@ def usage(): def parse_options(): parser = OptionParser() - (options, args) = parser.parse_args() + args = ipa.config.init_config(sys.argv) + options, args = parser.parse_args(args) return options, args def main(): user={} - (options, args) = parse_options() + options, args = parse_options() - if len(args) != 1: + if len(args) != 2: usage() try: - ent = ipa.rpcclient.get_user(args[0]) - for name, value in ent.items(): - if isinstance(value, str): - print name + ": " + value + client = ipaclient.IPAClient() + ent = client.get_user(args[1]) + attr = ent.attrList() + + print "dn: " + ent.dn + + for a in attr: + value = ent.getValues(a) + if isinstance(value,str): + print a + ": " + value else: - print name + ": " - for x in value: - print "\t" + x + print a + ": " + for l in value: + print "\t" + l + except xmlrpclib.Fault, fault: print fault.faultString + return 1 + except kerberos.GSSError, e: + print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) + return 1 return 0 diff --git a/ipa-python/Makefile b/ipa-python/Makefile index bc6554be4..b2e4660f8 100644 --- a/ipa-python/Makefile +++ b/ipa-python/Makefile @@ -1,11 +1,17 @@ PYTHONLIBDIR ?= $(shell python -c "from distutils.sysconfig import *; print get_python_lib(1)") PACKAGEDIR ?= $(DESTDIR)/$(PYTHONLIBDIR)/ipa +CONFIGDIR ?= $(DESTDIR)/etc/ipa all: ; install: -mkdir -p $(PACKAGEDIR) install -m 644 *.py $(PACKAGEDIR) + -mkdir -p $(CONFIGDIR) + if ! [ -e $(CONFIGDIR)/ipa.conf ]; then \ + install -m 644 ipa.conf $(CONFIGDIR); \ + fi clean: - rm -f *~ *.pyc \ No newline at end of file + rm -f *~ *.pyc + diff --git a/ipa-python/config.py b/ipa-python/config.py new file mode 100644 index 000000000..a17e585bc --- /dev/null +++ b/ipa-python/config.py @@ -0,0 +1,106 @@ +# Authors: Karl MacMillan +# +# 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 ConfigParser +from optparse import OptionParser + +class IPAConfigError(Exception): + def __init__(self, msg=''): + self.msg = msg + Exception.__init__(self, msg) + + def __repr__(self): + return self.msg + + __str__ = __repr__ + +class IPAConfig: + def __init__(self): + self.default_realm = None + self.default_server = None + + def get_realm(self): + if self.default_realm: + return self.default_realm + else: + raise IPAConfigError("no default realm") + + def get_server(self): + if self.default_server: + return self.default_server + else: + raise IPAConfigError("no default server") + +# Global library config +config = IPAConfig() + +def __parse_config(): + p = ConfigParser.SafeConfigParser() + p.read("/etc/ipa/ipa.conf") + + try: + config.default_realm = p.get("defaults", "realm") + config.default_server = p.get("defaults", "server") + except: + pass + +def usage(): + return """ --realm\tset the IPA realm + --server\tset the IPA server +""" + +def __parse_args(args): + # Can't use option parser because it doesn't easily leave + # unknown arguments - creating our own seems simpler. + # + # should make this more robust and handle --realm=foo syntax + out_args = [] + i = 0 + while i < len(args): + if args[i] == "--realm": + if i == len(args) - 1: + raise IPAConfigError("missing argument to --realm") + config.default_realm = args[i + 1] + i = i + 2 + continue + if args[i] == "--server": + if i == len(args) - 1: + raise IPAConfigError("missing argument to --server") + config.default_server = args[i + 1] + i = i + 2 + continue + out_args.append(args[i]) + i = i + 1 + + return out_args + + +def init_config(args=None): + __parse_config() + out_args = None + if args: + out_args = __parse_args(args) + + if not config.default_realm: + raise IPAConfigError("realm not specified in config file or on command line") + if not config.default_server: + raise IPAConfigError("server not specified in config file or on command line") + + if out_args: + return out_args diff --git a/ipa-python/freeipa-python.spec b/ipa-python/freeipa-python.spec index 61be3a5d6..e9b1e708d 100755 --- a/ipa-python/freeipa-python.spec +++ b/ipa-python/freeipa-python.spec @@ -1,6 +1,6 @@ Name: freeipa-python Version: 0.1.0 -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -15,6 +15,7 @@ Requires: python %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %define pkgpythondir %{python_sitelib}/ipa +%define configdir /etc/ipa %description FreeIPA is a server for identity, policy, and audit. @@ -25,6 +26,7 @@ FreeIPA is a server for identity, policy, and audit. %install rm -rf %{buildroot} mkdir -p %{buildroot}%{pkgpythondir} +mkdir -p %{buildroot}%{configdir} make install DESTDIR=%{buildroot} @@ -36,9 +38,17 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{pkgpythondir}/* +%config(noreplace) %{configdir}/ipa.conf %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Add User class +- Add kerberos authentication to the XML-RPC request made from tools. + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version diff --git a/ipa-python/freeipa-python.spec.in b/ipa-python/freeipa-python.spec.in index 90a135b45..b0a373085 100755 --- a/ipa-python/freeipa-python.spec.in +++ b/ipa-python/freeipa-python.spec.in @@ -1,6 +1,6 @@ Name: freeipa-python Version: VERSION -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -15,6 +15,7 @@ Requires: python %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %define pkgpythondir %{python_sitelib}/ipa +%define configdir /etc/ipa %description FreeIPA is a server for identity, policy, and audit. @@ -25,6 +26,7 @@ FreeIPA is a server for identity, policy, and audit. %install rm -rf %{buildroot} mkdir -p %{buildroot}%{pkgpythondir} +mkdir -p %{buildroot}%{configdir} make install DESTDIR=%{buildroot} @@ -36,9 +38,17 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{pkgpythondir}/* +%config(noreplace) %{configdir}/ipa.conf %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Add User class +- Add kerberos authentication to the XML-RPC request made from tools. + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version diff --git a/ipa-python/ipa.conf b/ipa-python/ipa.conf new file mode 100644 index 000000000..5243cf22f --- /dev/null +++ b/ipa-python/ipa.conf @@ -0,0 +1,3 @@ +[defaults] +realm = foo.bar +server = realm.foo.bar diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py new file mode 100644 index 000000000..c75b5bc96 --- /dev/null +++ b/ipa-python/ipaclient.py @@ -0,0 +1,87 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# 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 +# + +#!/usr/bin/python + +import sys +sys.path.append("/usr/share/ipa") + +from ipaserver import funcs +import ipa.rpcclient as rpcclient +import user +import ipa +import config + +class IPAClient: + + def __init__(self,local=None): + self.local = local + ipa.config.init_config() + if local: + self.transport = funcs.IPAServer() + # client needs to call set_principal(user@REALM) + else: + self.transport = rpcclient.RPCClient() + + def set_principal(self,princ): + if self.local: + self.transport.set_principal(princ) + + def get_user(self,uid): + result = self.transport.get_user(uid) + return user.User(result) + + def add_user(self,user): + + realm = config.config.get_realm() + + # FIXME: This should be dynamic and can include just about anything + # Let us add in some missing attributes + if user.get('homeDirectory') is None: + user['homeDirectory'] ='/home/%s' % user['uid'] + if user.get('gecos') is None: + user['gecos'] = user['uid'] + + # FIXME: This can be removed once the DS plugin is installed + user['uidNumber'] ='501' + + # FIXME: What is the default group for users? + user['gidNumber'] ='501' + user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm) + user['cn'] = "%s %s" % (user['givenName'], user['sn']) + if user.get('gn'): + del user['gn'] + + result = self.transport.add_user(user) + return result + + def get_all_users(self): + result = self.transport.get_all_users() + + all_users = [] + for (attrs) in result: + if attrs is not None: + all_users.append(user.User(attrs)) + + return all_users + + def get_add_schema(self): + result = self.transport.get_add_schema() + return result diff --git a/ipa-python/krbtransport.py b/ipa-python/krbtransport.py new file mode 100644 index 000000000..dbb8ec34e --- /dev/null +++ b/ipa-python/krbtransport.py @@ -0,0 +1,55 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden +# +# 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 +# + +#!/usr/bin/python + +import httplib +import xmlrpclib +import kerberos +from kerberos import GSSError + +class KerbTransport(xmlrpclib.Transport): + """Handles Kerberos Negotiation authentication to an XML-RPC server.""" + + def get_host_info(self, host): + + host, extra_headers, x509 = xmlrpclib.Transport.get_host_info(self, host) + + # Set the remote host principal + h = host + hostinfo = h.split(':') + service = "HTTP@" + hostinfo[0] + + try: + rc, vc = kerberos.authGSSClientInit(service); + except kerberos.GSSError, e: + raise GSSError(e) + + try: + kerberos.authGSSClientStep(vc, ""); + except kerberos.GSSError, e: + raise GSSError(e) + + extra_headers = [ + ("Authorization", "negotiate %s" % kerberos.authGSSClientResponse(vc) ) + ] + + return host, extra_headers, x509 + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index 416026628..d4e645e1e 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -20,83 +20,101 @@ #!/usr/bin/python -try: - import krbV -except ImportError: - pass import xmlrpclib import socket +import config +from krbtransport import KerbTransport +from kerberos import GSSError import os import base64 +import user +import ipa # Some errors to catch # http://cvs.fedora.redhat.com/viewcvs/ldapserver/ldap/servers/plugins/pam_passthru/README?root=dirsec&rev=1.6&view=auto - -# FIXME: do we want this set somewhere else? -server = xmlrpclib.ServerProxy("http://localhost:80/ipa") - -def get_user(username): - """Get a specific user""" - - try: - result = server.get_user(username) - myuser = result - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault.faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None - - return myuser - -def add_user(user): - """Add a new user""" - # FIXME: Get the realm from somewhere - realm="GREYOAK.COM" +class RPCClient: - # FIXME: This should be dynamic and can include just about anything - # Let us add in some missing attributes - if user.get('homeDirectory') is None: - user['homeDirectory'] ='/home/%s' % user['uid'] - if user.get('gecos') is None: - user['gecos'] = user['uid'] + def __init__(self): + ipa.config.init_config() + + def server_url(self): + return "http://" + config.config.get_server() + "/ipa" + + def setup_server(self): + return xmlrpclib.ServerProxy(self.server_url(), KerbTransport()) + + def convert_entry(self,ent): + # Convert into a dict. We need to handle multi-valued attributes as well + # so we'll convert those into lists. + user={} + for (k) in ent: + k = k.lower() + if user.get(k) is not None: + if isinstance(user[k],list): + user[k].append(ent[k].strip()) + else: + first = user[k] + user[k] = () + user[k].append(first) + user[k].append(ent[k].strip()) + else: + user[k] = ent[k] + + return user + + def get_user(self,username): + """Get a specific user""" + server = self.setup_server() + try: + result = server.get_user(username) + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) - # FIXME: This can be removed once the DS plugin is installed - user['uidNumber'] ='501' - - # FIXME: What is the default group for users? - user['gidNumber'] ='501' - user['krbPrincipalName'] = "%s@%s" % (user['uid'], realm) - user['cn'] = "%s %s" % (user['gn'], user['sn']) - if user.get('gn'): - del user['gn'] - - try: - result = server.add_user(user) return result - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault.faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None + + + def add_user(self,user): + """Add a new user""" + server = self.setup_server() -def get_add_schema(): - """Get the list of attributes we need to ask when adding a new - user. - """ + try: + result = server.add_user(user) + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return result + + def get_add_schema(self): + """Get the list of attributes we need to ask when adding a new + user. + """ + server = self.setup_server() + + # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want + # this for the CLI as well? + try: + result = server.get_add_schema() + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return result - # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want - # this for the CLI as well? - try: - result = server.get_add_schema() - except xmlrpclib.Fault, fault: - raise xmlrpclib.Fault(fault,faultCode, fault.faultString) - return None - except socket.error, (value, msg): - raise xmlrpclib.Fault(value, msg) - return None - - return result + def get_all_users (self): + """Return a list containing a User object for each existing user.""" + + server = self.setup_server() + try: + result = server.get_all_users() + except xmlrpclib.Fault, fault: + raise xmlrpclib.Fault(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return result diff --git a/ipa-python/user.py b/ipa-python/user.py new file mode 100644 index 000000000..6162354be --- /dev/null +++ b/ipa-python/user.py @@ -0,0 +1,112 @@ +import ldap +import ldif +import re +import cStringIO + +class User: + """This class represents an IPA user. An LDAP entry consists of a DN + and a list of attributes. Each attribute consists of a name and a list of + values. For the time being I will maintain this. + + In python-ldap, entries are returned as a list of 2-tuples. + Instance variables: + dn - string - the string DN of the entry + data - cidict - case insensitive dict of the attributes and values""" + + def __init__(self,entrydata): + """data is the raw data returned from the python-ldap result method, + which is a search result entry or a reference or None. + If creating a new empty entry, data is the string DN.""" + if entrydata: + if isinstance(entrydata,tuple): + self.dn = entrydata[0] + self.data = ldap.cidict.cidict(entrydata[1]) + elif isinstance(entrydata,str): + self.dn = entrydata + self.data = ldap.cidict.cidict() + elif isinstance(entrydata,dict): + self.dn = entrydata['dn'] + del entrydata['dn'] + self.data = ldap.cidict.cidict(entrydata) + else: + self.dn = '' + self.data = ldap.cidict.cidict() + + def __nonzero__(self): + """This allows us to do tests like if entry: returns false if there is no data, + true otherwise""" + return self.data != None and len(self.data) > 0 + + def hasAttr(self,name): + """Return True if this entry has an attribute named name, False otherwise""" + return self.data and self.data.has_key(name) + + def __getattr__(self,name): + """If name is the name of an LDAP attribute, return the first value for that + attribute - equivalent to getValue - this allows the use of + entry.cn + instead of + entry.getValue('cn') + This also allows us to return None if an attribute is not found rather than + throwing an exception""" + return self.getValue(name) + + def getValues(self,name): + """Get the list (array) of values for the attribute named name""" + return self.data.get(name) + + def getValue(self,name): + """Get the first value for the attribute named name""" + value = self.data.get(name,[None]) + if isinstance(value[0],list) or isinstance(value[0],tuple): + return value[0] + else: + return value + + def setValue(self,name,*value): + """Value passed in may be a single value, several values, or a single sequence. + For example: + ent.setValue('name', 'value') + ent.setValue('name', 'value1', 'value2', ..., 'valueN') + ent.setValue('name', ['value1', 'value2', ..., 'valueN']) + ent.setValue('name', ('value1', 'value2', ..., 'valueN')) + Since *value is a tuple, we may have to extract a list or tuple from that + tuple as in the last two examples above""" + if isinstance(value[0],list) or isinstance(value[0],tuple): + self.data[name] = value[0] + else: + self.data[name] = value + + setValues = setValue + + def toTupleList(self): + """Convert the attrs and values to a list of 2-tuples. The first element + of the tuple is the attribute name. The second element is either a + single value or a list of values.""" + return self.data.items() + + def attrList(self): + """Return a list of all attributes in the entry""" + return self.data.keys() + +# def __str__(self): +# """Convert the Entry to its LDIF representation""" +# return self.__repr__() +# +# # the ldif class base64 encodes some attrs which I would rather see in raw form - to +# # encode specific attrs as base64, add them to the list below +# ldif.safe_string_re = re.compile('^$') +# base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData'] +# +# def __repr__(self): +# """Convert the Entry to its LDIF representation""" +# sio = cStringIO.StringIO() +# # what's all this then? the unparse method will currently only accept +# # a list or a dict, not a class derived from them. self.data is a +# # cidict, so unparse barfs on it. I've filed a bug against python-ldap, +# # but in the meantime, we have to convert to a plain old dict for printing +# # I also don't want to see wrapping, so set the line width really high (1000) +# newdata = {} +# newdata.update(self.data) +# ldif.LDIFWriter(sio,User.base64_attrs,1000).unparse(self.dn,newdata) +# return sio.getvalue() diff --git a/ipa-server/freeipa-server.spec b/ipa-server/freeipa-server.spec index 4801eb7fe..463db5893 100755 --- a/ipa-server/freeipa-server.spec +++ b/ipa-server/freeipa-server.spec @@ -1,6 +1,6 @@ Name: freeipa-server Version: 0.1.0 -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -10,7 +10,7 @@ Source0: %{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch -Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python python-ldap freeipa-python +Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi %define httpd_conf /etc/httpd/conf.d @@ -23,7 +23,6 @@ FreeIPA is a server for identity, policy, and audit. %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_sbindir} -mkdir -p %{buildroot}%{httpd_conf} make install DESTDIR=%{buildroot} @@ -40,10 +39,19 @@ rm -rf %{buildroot} %dir %{_usr}/share/ipa %{_usr}/share/ipa/* -%{httpd_conf}/ipa.conf - %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Add mod_auth_kerb and cyrus-sasl-gssapi to Requires +- Remove references to admin server in ipa-server-setupssl +- Generate a client certificate for the XML-RPC server to connect to LDAP with +- Create a keytab for Apache +- Create an ldif with a test user +- Provide a certmap.conf for doing SSL client authentication + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version diff --git a/ipa-server/freeipa-server.spec.in b/ipa-server/freeipa-server.spec.in index 16aff06b7..549afc979 100644 --- a/ipa-server/freeipa-server.spec.in +++ b/ipa-server/freeipa-server.spec.in @@ -1,6 +1,6 @@ Name: freeipa-server Version: VERSION -Release: 1%{?dist} +Release: 3%{?dist} Summary: FreeIPA authentication server Group: System Environment/Base @@ -10,7 +10,7 @@ Source0: %{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch -Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python python-ldap freeipa-python +Requires: python fedora-ds-base krb5-server krb5-server-ldap nss-tools openldap-clients httpd mod_python mod_auth_kerb python-ldap freeipa-python ntpd cyrus-sasl-gssapi %define httpd_conf /etc/httpd/conf.d @@ -23,7 +23,6 @@ FreeIPA is a server for identity, policy, and audit. %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_sbindir} -mkdir -p %{buildroot}%{httpd_conf} make install DESTDIR=%{buildroot} @@ -40,10 +39,19 @@ rm -rf %{buildroot} %dir %{_usr}/share/ipa %{_usr}/share/ipa/* -%{httpd_conf}/ipa.conf - %changelog +* Mon Aug 5 2007 Rob Crittenden - 0.1.0-3 +- Abstracted client class to work directly or over RPC + +* Wed Aug 1 2007 Rob Crittenden - 0.1.0-2 +- Add mod_auth_kerb and cyrus-sasl-gssapi to Requires +- Remove references to admin server in ipa-server-setupssl +- Generate a client certificate for the XML-RPC server to connect to LDAP with +- Create a keytab for Apache +- Create an ldif with a test user +- Provide a certmap.conf for doing SSL client authentication + * Fri Jul 27 2007 Karl MacMillan - 0.1.0-1 - Initial rpm version diff --git a/ipa-server/ipa-install/Makefile b/ipa-server/ipa-install/Makefile index 0d4953976..877ae09cc 100644 --- a/ipa-server/ipa-install/Makefile +++ b/ipa-server/ipa-install/Makefile @@ -6,7 +6,8 @@ install: install -m 755 ipa-server-install $(SBINDIR) install -m 755 ipa-server-setupssl $(SBINDIR) $(MAKE) -C share $@ + $(MAKE) -C test $@ clean: $(MAKE) -C share $@ - rm -f *~ *.pyc \ No newline at end of file + rm -f *~ *.pyc diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install index 7abcafd8f..2fa9182bc 100644 --- a/ipa-server/ipa-install/ipa-server-install +++ b/ipa-server/ipa-install/ipa-server-install @@ -75,7 +75,7 @@ def logging_setup(options): formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) - + def main(): options = parse_options() logging_setup(options) @@ -119,6 +119,16 @@ def main(): # Restart apache run(["/sbin/service", "httpd", "restart"]) + # Set apache to be on at boot + run(["/sbin/chkconfig", "httpd", "on"]) + + # Create the config file + fd = open("/etc/ipa/ipa.conf", "w") + fd.write("[defaults]\n") + fd.write("server=" + host_name + "\n") + fd.write("realm=" + options.realm_name + "\n") + fd.close() + return 0 main() diff --git a/ipa-server/ipa-install/ipa-server-setupssl b/ipa-server/ipa-install/ipa-server-setupssl index f75327907..d7eb6f39f 100644 --- a/ipa-server/ipa-install/ipa-server-setupssl +++ b/ipa-server/ipa-install/ipa-server-setupssl @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash if [ "$1" ] ; then password=$1 @@ -49,22 +49,14 @@ if [ -f $secdir/cert8.db ] ; then needServerCert=1 fi - # look for admin server cert - if certutil -L -d $secdir -n "server-cert" 2> /dev/null ; then - echo "Using existing admin server-cert" - else - echo "No Admin Server Cert found - will create new one" - needASCert=1 - fi prefix="new-" prefixarg="-P $prefix" else needCA=1 needServerCert=1 - needASCert=1 fi -if test -z "$needCA" -a -z "$needServerCert" -a -z "$needASCert" ; then +if test -z "$needCA" -a -z "$needServerCert" ; then echo "No certs needed - exiting" exit 0 fi @@ -120,17 +112,17 @@ if test -n "$needServerCert" ; then certutil -S $prefixarg -n "Server-Cert" -s "cn=$myhost,ou=Fedora Directory Server" -c "CA certificate" -t "u,u,u" -m 1001 -v 120 -d $secdir -z $secdir/noise.txt -f $secdir/pwdfile.txt fi -if test -n "$needASCert" ; then -# Generate the admin server certificate - certutil -S $prefixarg -n "server-cert" -s "cn=$myhost,ou=Fedora Administration Server" -c "CA certificate" -t "u,u,u" -m 1002 -v 120 -d $secdir -z $secdir/noise.txt -f $secdir/pwdfile.txt +# 8. Generate the web service client certificate: + echo -e "0\n2\n9\nn\n0\n9\nn\n" | certutil -S $prefixarg -n webservice -s "uid=webservice, CN=Web Service, OU=Fedora Directory Server" -c "CA certificate" -t u,pu,u -m 1002 -v 120 -d $secdir -z $secdir/noise.txt -f $secdir/pwdfile.txt -1 -5 -# export the admin server certificate/private key for import into its key/cert db - pk12util -d $secdir $prefixarg -o $secdir/adminserver.p12 -n server-cert -w $secdir/pwdfile.txt -k $secdir/pwdfile.txt - if test -n "$isroot" ; then - chown $uid:$gid $secdir/adminserver.p12 - fi - chmod 400 $secdir/adminserver.p12 -fi + pk12util -d $secdir $prefixarg -o $secdir/webservice.p12 -n "webservice" -w $secdir/pwdfile.txt -k $secdir/pwdfile.txt + + openssl pkcs12 -in $secdir/webservice.p12 -clcerts -nokeys -out /usr/share/ipa/cert.pem -passin file:$secdir/pwdfile.txt + openssl pkcs12 -in $secdir/webservice.p12 -nocerts -nodes -out /usr/share/ipa/key.pem -passin file:$secdir/pwdfile.txt + + cp -p $secdir/cacert.asc /usr/share/ipa + chown apache:apache /usr/share/ipa/cert.pem /usr/share/ipa/key.pem /usr/share/ipa/cacert.asc + chmod 600 /usr/share/ipa/cert.pem /usr/share/ipa/key.pem # create the pin file if [ ! -f $secdir/pin.txt ] ; then @@ -153,42 +145,6 @@ if [ -n "$prefix" ] ; then mv $secdir/${prefix}key3.db $secdir/key3.db fi -# create the admin server key/cert db -asprefix=admin-serv- -if [ ! -f ${asprefix}cert8.db ] ; then - certutil -N -d $secdir -P $asprefix -f $secdir/pwdfile.txt - if test -n "$isroot" ; then - chown $uid:$gid $secdir/admin-serv-*.db - fi - chmod 600 $secdir/admin-serv-*.db -fi - -if test -n "$needASCert" ; then -# import the admin server key/cert - pk12util -d $secdir -P $asprefix -n server-cert -i $secdir/adminserver.p12 -w $secdir/pwdfile.txt -k $secdir/pwdfile.txt - -# import the CA cert to the admin server cert db - certutil -A -d $secdir -P $asprefix -n "CA certificate" -t "CT,," -a -i $secdir/cacert.asc -fi - -if [ ! -f $secdir/password.conf ] ; then -# create the admin server password file - echo 'internal:'`cat $secdir/pwdfile.txt` > $secdir/password.conf - if test -n "$isroot" ; then - chown $uid:$gid $secdir/password.conf - fi - chmod 400 $secdir/password.conf -fi - -# tell admin server to use the password file -if [ -f ../admin-serv/config/nss.conf ] ; then - sed -e "s@^NSSPassPhraseDialog .*@NSSPassPhraseDialog file:`pwd`/password.conf@" ../admin-serv/config/nss.conf > /tmp/nss.conf && mv /tmp/nss.conf ../admin-serv/config/nss.conf - if test -n "$isroot" ; then - chown $uid:$gid ../admin-serv/config/nss.conf - fi - chmod 400 ../admin-serv/config/nss.conf -fi - # enable SSL in the directory server ldapmodify -x -h localhost -p $ldapport -D "cn=Directory Manager" -w $password < +# : [] +# : [] +# +# Notes: +# +# 1. Mapping can be defined per issuer of a certificate. If mapping doesn't +# exists for a particular 'issuerDN' then the server uses the default +# mapping. +# +# 2. There must be an entry for =default and issuerDN "default". +# This mapping is the default mapping. +# +# 3. '#' can be used to comment out a line. +# +# 4. DNComps & FilterComps are used to form the base DN and filter resp. for +# performing an LDAP search while mapping the cert to a user entry. +# +# 5. DNComps can be one of the following: +# commented out - take the user's DN from the cert as is +# empty - search the entire LDAP tree (DN == suffix) +# attr names - a comma separated list of attributes to form DN +# +# 6. FilterComps can be one of the following: +# commented out - set the filter to "objectclass=*" +# empty - set the filter to "objectclass=*" +# attr names - a comma separated list of attributes to form the filter +# + +certmap default default +#default:DNComps +#default:FilterComps e, uid +#default:verifycert on +#default:CmapLdapAttr certSubjectDN +#default:library +#default:InitFn +default:DNComps +default:FilterComps uid diff --git a/ipa-server/ipa-install/share/default-aci.ldif b/ipa-server/ipa-install/share/default-aci.ldif index 8916833c8..a32729a3b 100644 --- a/ipa-server/ipa-install/share/default-aci.ldif +++ b/ipa-server/ipa-install/share/default-aci.ldif @@ -7,4 +7,6 @@ aci: (targetattr="carLicense ||description ||displayName ||facsimileTelephoneNum aci: (targetattr="krbPrincipalKey")(version 3.0; acl "KDC System Account"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=kerberos,$SUFFIX";) aci: (targetattr="*")(version 3.0; acl "Directory Administrators can manage all entries"; allow(all)groupdn="ldap:///cn=Directory Administrators,$SUFFIX";) aci: (targetattr="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword")(version 3.0; acl "Kpasswd access to passowrd hashes for passowrd changes"; allow (all) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) - +aci: (target="ldap:///uid=*,ou=users,ou=default,$SUFFIX")(targetattr="*")(version 3.0; acl "allowproxy-webservice"; allow (proxy) userdn="ldap:///uid=webservice,ou=special,$SUFFIX";) +aci: (target="ldap:///uid=*,ou=users,ou=default,$SUFFIX")(targetattr="*")(version 3.0; acl "admins can write entries"; allow(add,delete,write)groupdn="ldap:///cn=admin,ou=groups,ou=default,$SUFFIX";) +aci: (targetattr="userPrincipal")(version 3.0; acl "allow webservice to find users by kerberos principal name"; allow (read, search) userdn="ldap:///uid=webservice,ou=special,$SUFFIX";) diff --git a/ipa-server/ipa-install/test/Makefile b/ipa-server/ipa-install/test/Makefile new file mode 100644 index 000000000..696ae771e --- /dev/null +++ b/ipa-server/ipa-install/test/Makefile @@ -0,0 +1,8 @@ +SHAREDIR = $(DESTDIR)/usr/share/ipa + +install: + -mkdir -p $(SHAREDIR) + install -m 644 *.ldif $(SHAREDIR) + +clean: + rm -f *~ diff --git a/ipa-server/ipa-install/test/test-users.ldif b/ipa-server/ipa-install/test/test-users-template.ldif similarity index 60% rename from ipa-server/ipa-install/test/test-users.ldif rename to ipa-server/ipa-install/test/test-users-template.ldif index 424eedb55..0057d9766 100644 --- a/ipa-server/ipa-install/test/test-users.ldif +++ b/ipa-server/ipa-install/test/test-users-template.ldif @@ -1,5 +1,6 @@ # test, users, default, $REALM dn: uid=test,ou=users,ou=default,$SUFFIX +changetype: add uidNumber: 1001 uid: test gecos: test @@ -13,8 +14,17 @@ shadowInactive: -1 shadowLastChange: 13655 shadowFlag: -1 gidNumber: 100 +objectclass: krbPrincipalAux +objectclass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount objectClass: account objectClass: top -cn: test +cn: Test User +sn: User +krbPrincipalName: test@$REALM + +dn: cn=admin,ou=groups,ou=default,$SUFFIX +changetype: modify +add: uniqueMember +uniqueMember: uid=test,ou=users,ou=default,$SUFFIX diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py index 775a2f2b3..face142a6 100644 --- a/ipa-server/ipaserver/dsinstance.py +++ b/ipa-server/ipaserver/dsinstance.py @@ -88,8 +88,10 @@ class DsInstance: self.__create_instance() self.__add_default_schemas() self.__enable_ssl() + self.__certmap_conf() self.restart() self.__add_default_layout() + self.__create_test_users() def config_dirname(self): if not self.serverid: @@ -136,7 +138,7 @@ class DsInstance: args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name] logging.debug("calling setup-ds.pl") else: - args = ["/usr/sbin/ds_newinst.pl", inf_fd.name] + args = ["/usr/bin/ds_newinst.pl", inf_fd.name] logging.debug("calling ds_newinst.pl") run(args) logging.debug("completed creating ds instance") @@ -166,3 +168,21 @@ class DsInstance: "-w", self.admin_password, "-f", inf_fd.name] run(args) logging.debug("done adding default ds layout") + + def __create_test_users(self): + logging.debug("create test users ldif") + txt = template_file(SHARE_DIR + "test-users-template.ldif", self.sub_dict) + user_fd = open(SHARE_DIR+"test-users.ldif", "w") + user_fd.write(txt) + user_fd.close() + logging.debug("done creating test users ldif") + + def __certmap_conf(self): + logging.debug("configuring certmap.conf for ds instance") + dirname = self.config_dirname() + certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict) + certmap_fd = open(dirname+"certmap.conf", "w+") + certmap_fd.write(certmap_conf) + certmap_fd.close() + + logging.debug("done configuring certmap.conf for ds instance") diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index f440ae4bb..ee0388cab 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -1,6 +1,6 @@ #! /usr/bin/python -E # Authors: Rich Megginson -# Rob Crittenden -# AuthType Kerberos -# AuthName "Kerberos Login" -# KrbMethodNegotiate on -# KrbMethodK5Passwd off -# KrbServiceName HTTP -# KrbAuthRealms GREYOAK.COM -# Krb5KeyTab /etc/httpd/conf/ipa.keytab -# KrbSaveCredentials on -# Require valid-user + AuthType Kerberos + AuthName "Kerberos Login" + KrbMethodNegotiate on + KrbMethodK5Passwd off + KrbServiceName HTTP + KrbAuthRealms $REALM + Krb5KeyTab /etc/httpd/conf/ipa.keytab + KrbSaveCredentials on + Require valid-user ErrorDocument 401 /errors/unauthorized.html SetHandler mod_python diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index ad5e30683..7bad9ab5e 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -123,11 +123,16 @@ class ModXMLRPCRequestHandler(object): def register_instance(self,instance): self.register_module(instance) - def _marshaled_dispatch(self, data): + def _marshaled_dispatch(self, data, remoteuser): """Dispatches an XML-RPC method from marshalled (XML) data.""" params, method = loads(data) + opts={} + opts['remoteuser'] = remoteuser + + params = ipaserver.encode_args(params, opts) + # special case # if method == "get_user": # Marshaller._Marshaller__dump = xmlrpclib_dump @@ -239,7 +244,7 @@ class ModXMLRPCRequestHandler(object): req.allow_methods(['POST'],1) raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED - response = self._marshaled_dispatch(req.read()) + response = self._marshaled_dispatch(req.read(), req.user) req.content_type = "text/xml" req.set_content_length(len(response)) @@ -267,10 +272,12 @@ def handler(req, profiling=False): else: opts = req.get_options() try: + f = funcs.IPAServer() h = ModXMLRPCRequestHandler() - h.register_function(funcs.get_user) - h.register_function(funcs.add_user) - h.register_function(funcs.get_add_schema) + h.register_function(f.get_user) + h.register_function(f.add_user) + h.register_function(f.get_add_schema) + h.register_function(f.get_all_users) h.handle_request(req) finally: pass