diff options
author | rcritten@redhat.com <rcritten@redhat.com> | 2007-09-05 13:14:23 -0400 |
---|---|---|
committer | rcritten@redhat.com <rcritten@redhat.com> | 2007-09-05 13:14:23 -0400 |
commit | 9b30f4674465b8e5f9bfcb359a9a9336dec0d120 (patch) | |
tree | 6cd0e8e521b315e17484445cd9b6507b5727244c /ipa-server/xmlrpc-server | |
parent | 82943c31de32b0388a7c23e6e6a18cc9c99ac502 (diff) | |
download | freeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.tar.gz freeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.tar.xz freeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.zip |
Enable LDAP SASL authentication using a forwarded kerberos ticket
Handle both SASL auth and proxied authentication
Refactor LDAP connection code to be simpler
Other small bug fixes
Diffstat (limited to 'ipa-server/xmlrpc-server')
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 237 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 12 |
2 files changed, 128 insertions, 121 deletions
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index f4e01b342..294b5b5f7 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -47,20 +47,31 @@ DefaultGroupContainer = "cn=groups,cn=accounts" # this is not anticipated. class IPAConnPool: def __init__(self): - self.numentries = 0 self.freelist = [] - def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None): - self.numentries = self.numentries + 1 + def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None, keytab=None): + conn = None if len(self.freelist) > 0: - conn = self.freelist.pop() - else: + for i in range(len(self.freelist)): + c = self.freelist[i] + if ((c.host == host) and (c.port == port)): + conn = self.freelist.pop(i) + break + if conn is None: conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey) - conn.set_proxydn(proxydn) + if proxydn is not None: + conn.set_proxydn(proxydn) + else: + conn.set_keytab(keytab) return conn def releaseConn(self, conn): - self.freelist.append(conn) + # We can't re-use SASL connections. If proxydn is None it means + # we have a keytab set. See ipaldap.set_keytab + if conn.proxydn is None: + conn.unbind_s() + else: + self.freelist.append(conn) class IPAServer: @@ -68,7 +79,8 @@ class IPAServer: global _LDAPPool # FIXME, this needs to be auto-discovered self.host = 'localhost' - self.port = 636 + self.port = 389 + self.sslport = 636 self.bindcert = "/usr/share/ipa/cert.pem" self.bindkey = "/usr/share/ipa/key.pem" self.bindca = "/usr/share/ipa/cacert.asc" @@ -79,24 +91,84 @@ class IPAServer: self.basedn = ipa.ipautil.realm_to_suffix(ipa.config.config.get_realm()) self.scope = ldap.SCOPE_SUBTREE self.princ = None + self.keytab = None def set_principal(self, princ): self.princ = princ + + def set_keytab(self, keytab): + self.keytab = keytab def get_dn_from_principal(self, princ): - """Given a kerberls principal get the LDAP uid""" + """Given a kerberos principal get the LDAP uid""" global _LDAPPool filter = "(krbPrincipalName=" + princ + ")" # The only anonymous search we should have - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None) + conn = _LDAPPool.getConn(self.host,self.sslport,self.bindca,self.bindcert,self.bindkey,None,None) try: - ent = m1.getEntry(self.basedn, self.scope, filter, ['dn']) + ent = conn.getEntry(self.basedn, self.scope, filter, ['dn']) finally: - _LDAPPool.releaseConn(m1) + _LDAPPool.releaseConn(conn) return "dn:" + ent.dn + def __setup_connection(self, opts): + """Set up common things done in the connection. + If there is a keytab then return None as the proxy dn and the keytab + otherwise return the proxy dn and None as the keytab. + + We only want one or the other used at one time and we prefer + the keytab. So if there is a keytab, return that and None for + proxy dn to make calling getConn() easier. + """ + + if opts: + if opts.get('keytab'): + self.set_keytab(opts['keytab']) + self.set_principal(None) + else: + self.set_keytab(None) + self.set_principal(opts['remoteuser']) + else: + self.set_keytab(None) + # The caller should have already set the principal + + if self.princ is not None: + return self.get_dn_from_principal(self.princ), None + else: + return None, self.keytab + + def getConnection(self, opts): + """Wrapper around IPAConnPool.getConn() so we don't have to pass + around self.* every time a connection is needed. + + For SASL connections (where we have a keytab) we can't set + the SSL variables for certificates. It confuses the ldap + module. + """ + global _LDAPPool + + (proxy_dn, keytab) = self.__setup_connection(opts) + + if keytab is not None: + bindca = None + bindcert = None + bindkey = None + port = self.port + else: + bindca = self.bindca + bindcert = self.bindcert + bindkey = self.bindkey + port = self.sslport + + return _LDAPPool.getConn(self.host,port,bindca,bindcert,bindkey,proxy_dn,keytab) + + def releaseConnection(self, conn): + global _LDAPPool + + _LDAPPool.releaseConn(conn) + def convert_entry(self, ent): entry = dict(ent.data) entry['dn'] = ent.dn @@ -110,24 +182,17 @@ class IPAServer: entry[key] = value[0] return entry - def __get_entry (self, base, filter, sattrs=None, opts=None): """Get a specific entry. Return as a dict of values. Multi-valued fields are represented as lists. """ - global _LDAPPool ent="" - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - ent = m1.getEntry(base, self.scope, filter, sattrs) + ent = conn.getEntry(base, self.scope, filter, sattrs) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return self.convert_entry(ent) @@ -137,8 +202,6 @@ class IPAServer: oldentry is a dict newentry is a dict """ - global _LDAPPool - oldentry = self.convert_scalar_values(oldentry) newentry = self.convert_scalar_values(newentry) @@ -150,16 +213,11 @@ class IPAServer: except KeyError, e: raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN) - if opts: - self.set_principal(opts['remoteuser']) - - proxydn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn) + conn = self.getConnection(opts) try: - res = m1.updateEntry(moddn, oldentry, newentry) + res = conn.updateEntry(moddn, oldentry, newentry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def __safe_filter(self, criteria): @@ -234,8 +292,6 @@ class IPAServer: attribute name and the value is either a string or in the case of a multi-valued field a list of values. user_container sets where in the tree the user is placed.""" - global _LDAPPool - if user_container is None: user_container = DefaultUserContainer @@ -288,16 +344,11 @@ class IPAServer: for u in user: entry.setValues(u, user[u]) - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - res = m1.addEntry(entry) + res = conn.addEntry(entry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def get_add_schema (self): @@ -348,20 +399,13 @@ class IPAServer: """Return a list containing a User object for each existing user. """ - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - filter = "(objectclass=posixAccount)" - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - all_users = m1.getList(self.basedn, self.scope, filter, None) + all_users = conn.getList(self.basedn, self.scope, filter, None) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) users = [] for u in all_users: @@ -372,13 +416,6 @@ class IPAServer: def find_users (self, criteria, sattrs=None, opts=None): """Returns a list: counter followed by the results. If the results are truncated, counter will be set to -1.""" - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - # Assume the list of fields to search will come from a central # configuration repository. A good format for that would be # a comma-separated list of fields @@ -394,21 +431,21 @@ class IPAServer: (exact_match_filter, partial_match_filter) = self.__generate_match_filters( search_fields, criteria_words) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: try: - exact_results = m1.getListAsync(self.basedn, self.scope, + exact_results = conn.getListAsync(self.basedn, self.scope, exact_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): exact_results = [0] try: - partial_results = m1.getListAsync(self.basedn, self.scope, + partial_results = conn.getListAsync(self.basedn, self.scope, partial_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): partial_results = [0] finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) exact_counter = exact_results[0] partial_counter = partial_results[0] @@ -450,13 +487,6 @@ class IPAServer: def mark_user_deleted (self, uid, opts=None): """Mark a user as inactive in LDAP. We aren't actually deleting users here, just making it so they can't log in, etc.""" - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - proxydn = self.get_dn_from_principal(self.princ) - user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts) # Are we doing an add or replace operation? @@ -467,11 +497,11 @@ class IPAServer: else: has_key = False - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn) + conn = self.getConnection(opts) try: - res = m1.inactivateEntry(user['dn'], has_key) + res = conn.inactivateEntry(user['dn'], has_key) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) return res def delete_user (self, uid, opts=None): @@ -483,18 +513,15 @@ class IPAServer: The memberOf plugin handles removing the user from any other groups. """ - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts) if user_dn is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) - res = m1.deleteEntry(user_dn['dn']) - _LDAPPool.releaseConn(m1) + conn = self.getConnection(opts) + try: + res = conn.deleteEntry(user_dn['dn']) + finally: + self.releaseConnection(conn) return res # Group support @@ -532,8 +559,6 @@ class IPAServer: attribute name and the value is either a string or in the case of a multi-valued field a list of values. group_container sets where in the tree the group is placed.""" - global _LDAPPool - if group_container is None: group_container = DefaultGroupContainer @@ -554,38 +579,26 @@ class IPAServer: for g in group: entry.setValues(g, group[g]) - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - res = m1.addEntry(entry) + res = conn.addEntry(entry) finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) def find_groups (self, criteria, sattrs=None, opts=None): """Return a list containing a User object for each existing group that matches the criteria. """ - global _LDAPPool - - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - criteria = self.__safe_filter(criteria) filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + conn = self.getConnection(opts) try: - results = m1.getList(self.basedn, self.scope, filter, sattrs) + results = conn.getList(self.basedn, self.scope, filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): results = [] finally: - _LDAPPool.releaseConn(m1) + self.releaseConnection(conn) groups = [] for u in results: @@ -599,9 +612,6 @@ class IPAServer: group is the cn of the group to be added to """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(group, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) @@ -652,9 +662,6 @@ class IPAServer: group is the cn of the group to be removed from """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(group, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) @@ -718,19 +725,16 @@ class IPAServer: The memberOf plugin handles removing the group from any other groups. """ - if opts: - self.set_principal(opts['remoteuser']) - - dn = self.get_dn_from_principal(self.princ) - group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts) if len(group) != 1: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) - m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) - res = m1.deleteEntry(group[0]['dn']) - _LDAPPool.releaseConn(m1) + conn = self.getConnection(opts) + try: + res = conn.deleteEntry(group[0]['dn']) + finally: + self.releaseConnection(conn) return res def add_group_to_group(self, group, tgroup, opts=None): @@ -739,9 +743,6 @@ class IPAServer: tgroup is the cn of the group to be added to """ - if opts: - self.set_principal(opts['remoteuser']) - old_group = self.get_group_by_cn(tgroup, None, opts) if old_group is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 5dc60b51b..f2ddd35e8 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -126,13 +126,19 @@ class ModXMLRPCRequestHandler(object): def register_instance(self,instance): self.register_module(instance) - def _marshaled_dispatch(self, data, remoteuser): + def _marshaled_dispatch(self, data, req): """Dispatches an XML-RPC method from marshalled (XML) data.""" params, method = loads(data) + # Populate the Apache environment variables + req.add_common_vars() + opts={} - opts['remoteuser'] = remoteuser + opts['remoteuser'] = req.user + + if req.subprocess_env.get("KRB5CCNAME") is not None: + opts['keytab'] = req.subprocess_env.get("KRB5CCNAME") # Tack onto the end of the passed-in arguments any options we also # need @@ -263,7 +269,7 @@ class ModXMLRPCRequestHandler(object): req.allow_methods(['POST'],1) raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED - response = self._marshaled_dispatch(req.read(), req.user) + response = self._marshaled_dispatch(req.read(), req) req.content_type = "text/xml" req.set_content_length(len(response)) |