diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2013-11-29 22:41:56 +0100 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2015-06-07 02:50:58 +0200 |
commit | 89958abf4bc85a4e376cc68d98a721604af1ea77 (patch) | |
tree | 19b8930f0291518a0038e37fd605ed156a0bd21f /lib/openldap | |
parent | 3d8b0ac104dee68b47d9a4d2ef622e7f1acdd7a4 (diff) |
Allow flexible ACLs for SASL's EXTERNAL mechanism.
"username=postfix,cn=peercred,cn=external,cn=auth" is replaced by
"gidNumber=106+uidNumber=102,cn=peercred,cn=external,cn=auth" where 102
is postfix's UID and 106 its primary GID (looked up from /etc/passwd).
Diffstat (limited to 'lib/openldap')
-rw-r--r-- | lib/openldap | 31 |
1 files changed, 30 insertions, 1 deletions
diff --git a/lib/openldap b/lib/openldap index 5a4cef4..983299a 100644 --- a/lib/openldap +++ b/lib/openldap @@ -5,65 +5,87 @@ # # 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, either version 3 of the License, or # (at your option) any later version. # # 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, see <http://www.gnu.org/licenses/>. import ldap, ldap.sasl from ldap.filter import filter_format from ldap.dn import dn2str,explode_dn,str2dn from ldap.modlist import addModlist from ldif import LDIFParser from functools import partial -import re +import re, pwd # Dirty hack to check equality between the targetted LDIF and that # currently in the directory. The value of some configuration (olc*) # attributes is automatically indexed when added; for those we'll add # explicit indices to what we find in the LDIF. indexedAttributes = frozenset([ 'olcAttributeTypes', 'olcObjectClasses', 'olcAccess', ]) # Another hack. Configuration entries sometimes pollutes the DNs with # indices, thus it's not possible to directly use them as base. # Instead, we use their parent as a pase, and search for the *unique* # match with the same ObjectClass and the matching extra attributes. # ('%s' in the attribute value is replaced with the value of the source # entry.) indexedDN = { 'olcSchemaConfig': [('cn', '{*}%s')], 'olcHdbConfig': [('olcDbDirectory', '%s' )], } +# Allow for flexible ACLs for user using SASL's EXTERNAL mechanism. +# "username=postfix,cn=peercred,cn=external,cn=auth" is replaced by +# "gidNumber=106+uidNumber=102,cn=peercred,cn=external,cn=auth" where +# 102 is postfix's UID and 106 its primary GID. +# (Regular expressions are not allowed.) +sasl_ext_re = re.compile( r"""(?P<start>\sby\s+dn(?:\.exact)?)= + (?P<quote>['\"]?)username=(?P<user>[a-z][-a-z0-9_]*), + (?P<end>cn=peercred,cn=external,cn=auth) + (?P=quote)\s""" + , re.VERBOSE ) +pwd_dict = {} + +def acl_sasl_ext(m): + u = m.group('user') + if u not in pwd_dict.keys(): + pwd_dict[u] = pwd.getpwnam(u) + return '%s="gidNumber=%d+uidNumber=%d,%s" ' % ( m.group('start') + , pwd_dict[u].pw_gid + , pwd_dict[u].pw_uid + , m.group('end') + ) + # Run the given callback on each DN seen. If its return value is not # None, update the changed variable. class LDIFCallback(LDIFParser): def __init__(self, module, input, callback): LDIFParser.__init__(self,input) self.callback = callback self.changed = False def handle(self,dn,entry): b = self.callback(dn,entry) if b is not None: self.changed |= b # Run slapcat(8) on the given suffix or DB number (suffix takes # precedence) with an optional filter. (This is useful for offline # searches, or one needs to bypass ACLs.) Returns an open pipe to the # subprocess. def slapcat(filter=None, suffix=None, idx=0): @@ -132,40 +154,45 @@ def flexibleSearch(module, l, dn, entry): # directory with disclose/search/write access. def processEntry(module, l, dn, entry): changed = False r = flexibleSearch( module, l, dn, entry ) if r is None: changed = True if module.check_mode: module.exit_json(changed=changed, msg="add DN %s" % dn) l.add_s( dn, addModlist(entry) ) else: d,e = r fst = str2dn(dn).pop(0)[0][0] diff = [] for a,v in e.iteritems(): if a not in entry.keys(): if a != fst: # delete all values except for the first attribute, # which is implicit diff.append(( ldap.MOD_DELETE, a, None )) elif a in indexedAttributes: + if a == 'olcAccess': + # replace "username=...,cn=peercred,cn=external,cn=auth" + # by a DN with proper gidNumber and uidNumber + entry[a] = map ( partial(re.sub, sasl_ext_re, acl_sasl_ext) + , entry[a] ) # add explicit indices in the entry from the LDIF entry[a] = map( (lambda x: '{%d}%s' % x) , zip(range(len(entry[a])),entry[a]) ) if v != entry[a]: diff.append(( ldap.MOD_REPLACE, a, entry[a] )) elif v != entry[a]: # for non-indexed attribute, we update values in the # symmetric difference only s1 = set(v) s2 = set(entry[a]) if s1.isdisjoint(s2): # replace the former values with the new ones diff.append(( ldap.MOD_REPLACE, a, entry[a] )) else: x = list(s1.difference(s2)) if x: diff.append(( ldap.MOD_DELETE, a, x )) y = list(s2.difference(s1)) if y: diff.append(( ldap.MOD_ADD, a, y )) @@ -293,27 +320,29 @@ def main(): elif state == "present": if target is not None: # bind only once per LDIF file to l = ldap.initialize( 'ldapi://' ) l.sasl_interactive_bind_s('', ldap.sasl.external()) parser = LDIFCallback( module, open(target, 'r') , partial(processEntry,module,l) ) parser.parse() l.unbind_s() changed = parser.changed elif mod is not None: changed = loadModule(module, mod) else: module.fail_json(msg="missing target or module") except subprocess.CalledProcessError, e: module.fail_json(rv=e.returncode, msg=e.output.rstrip()) except ldap.LDAPError, e: module.fail_json(msg=e.args[0]['info']) + except KeyError, e: + module.fail_json(msg=str(e)) module.exit_json(changed=changed) # this is magic, see lib/ansible/module_common.py #<<INCLUDE_ANSIBLE_MODULE_COMMON>> main() |