summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/modules/openldap23
-rw-r--r--roles/LDAP-provider/files/etc/ldap/dynlist.ldif26
-rw-r--r--roles/LDAP-provider/tasks/main.yml12
-rw-r--r--roles/common-LDAP/templates/etc/ldap/database.ldif.j22
4 files changed, 55 insertions, 8 deletions
diff --git a/lib/modules/openldap b/lib/modules/openldap
index c09e791..f24a802 100644
--- a/lib/modules/openldap
+++ b/lib/modules/openldap
@@ -21,40 +21,41 @@ 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, pwd
import tempfile, atexit
# 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',
'olcSyncrepl',
'olcOverlay',
'olcLimits',
'olcAuthzRegexp',
+ 'olcDlAttrSet',
'olcDbConfig',
])
# 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 base, 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 = {
b'olcSchemaConfig': [('cn', '{*}%s')],
b'olcMdbConfig': [('olcDbDirectory', '%s' )],
b'olcOverlayConfig': [('olcOverlay', '%s' )],
b'olcMonitorConfig': [],
}
# 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
@@ -139,57 +140,69 @@ def processEntry(module, l, dn, entry):
for x in indexedAttributes.intersection(entry.keys()):
# remove useless extra spaces in ACLs etc
entry[x] = list(map( partial(multispaces.sub, b' '), entry[x] ))
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)
if 'olcAccess' in entry.keys():
# replace "username=...,cn=peercred,cn=external,cn=auth"
# by a DN with proper gidNumber and uidNumber
entry['olcAccess'] = list(map ( partial(sasl_ext_re.sub, acl_sasl_ext)
, entry['olcAccess'] ))
l.add_s( dn, addModlist(entry) )
else:
d,e = r
fst = str2dn(dn).pop(0)[0][0]
diff = []
+ re1 = re.compile( b'^(\{[0-9]+\})', re.I )
for a,v in e.items():
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] = list(map ( partial(sasl_ext_re.sub, acl_sasl_ext)
, entry[a] ))
- # add explicit indices in the entry from the LDIF
- entry[a] = list(map( (lambda x: b'{%d}%s' % x)
- , zip(range(len(entry[a])),entry[a])))
- if v != entry[a]:
- diff.append(( ldap.MOD_REPLACE, a, entry[a] ))
+ if a == fst:
+ if len(entry[a]) != 1 or len(v) != 1:
+ module.fail_json(msg=f'{len(entry[a])} != 1 or {len(v)} != 1')
+ m1 = re1.match(v[0])
+ if m1 is None:
+ module.fail_json(msg=f'{v[0]} is not indexed??')
+ else:
+ entry[a][0] = m1.group(1) + entry[a][0]
+ if entry[a] != v:
+ module.fail_json(msg=f'{entry[a]} != {v}, use modrdn to modifify the RDN (unimplemented)')
+ else:
+ # add explicit indices in the entry from the LDIF
+ entry[a] = list(map( (lambda x: b'{%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 ))
# add attributes that weren't in e
for a in set(entry).difference(e.keys()):
diff.append(( ldap.MOD_ADD, a, entry[a] ))
diff --git a/roles/LDAP-provider/files/etc/ldap/dynlist.ldif b/roles/LDAP-provider/files/etc/ldap/dynlist.ldif
new file mode 100644
index 0000000..df9a806
--- /dev/null
+++ b/roles/LDAP-provider/files/etc/ldap/dynlist.ldif
@@ -0,0 +1,26 @@
+# References:
+# - https://www.openldap.org/doc/admin24/overlays.html#Dynamic%20Lists
+# - man 5 slapo-dynlist
+
+# TODO bookworm (slapd 2.5)
+# “The dynlist overlay has been reworked with the 2.5 release to use a
+# consistent namespace as with other overlays. As a side-effect the
+# following cn=config parameters are deprecated and will be removed in a
+# future release: olcDlAttrSet is replaced with olcDynListAttrSet
+# olcDynamicList is replaced with olcDynListConfig”
+#
+# XXX that didn't solve the spaming from nextcloud's user_ldap plugin,
+# so we disable activity mails for “Your group memberships were
+# modified“ for now. See also
+#
+# https://github.com/nextcloud/server/issues/42195
+# https://github.com/nextcloud/server/issues/29832
+#
+# TODO bookworm: use “dynlist-attrset groupOfURLs memberURL
+# member+memberOf@groupOfNames” to also populate memberOf
+#
+dn: olcOverlay=dynlist,olcDatabase={*}mdb,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcDynamicList
+olcOverlay: dynlist
+olcDlAttrSet: groupOfURLs memberURL member
diff --git a/roles/LDAP-provider/tasks/main.yml b/roles/LDAP-provider/tasks/main.yml
index 9bc227e..8d4e327 100644
--- a/roles/LDAP-provider/tasks/main.yml
+++ b/roles/LDAP-provider/tasks/main.yml
@@ -1,21 +1,27 @@
- name: Load and configure the syncprov overlay
openldap: module=syncprov
suffix=dc=fripost,dc=org
target=etc/ldap/syncprov.ldif
local=file
+#- name: Load dyngroup schema
+# openldap: target=/etc/ldap/schema/dyngroup.ldif
+
+- name: Load and configure the dynlist overlay
+ openldap: module=dynlist
+ suffix=dc=fripost,dc=org
+ target=etc/ldap/dynlist.ldif
+ local=file
+
## XXX should be /etc/sasl2/slapd.conf ideally, but it doesn't work with
## Stretch, cf #211156 and #798462:
## ldapsearch -LLLx -H ldapi:// -b "" -s base supportedSASLMechanisms
- name: Enable the EXTERNAL SASL mechanism
lineinfile: dest=/usr/lib/sasl2/slapd.conf
regexp='^mech_list{{':'}}'
line='mech_list{{':'}} EXTERNAL'
create=yes
owner=root group=root
mode=0644
-#- name: Load dyngroup schema
-# openldap: target=/etc/ldap/schema/dyngroup.ldif
-
# TODO: authz constraint
diff --git a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2 b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
index a0ac705..f10bb33 100644
--- a/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
+++ b/roles/common-LDAP/templates/etc/ldap/database.ldif.j2
@@ -521,40 +521,42 @@ olcAccess: to dn.regex="^fvl=[^,]+,fvd=[^,]+,ou=virtual,dc=fripost,dc=org$"
{% if 'LDAP_provider' in group_names %}
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Export Fripost members to Nextcloud
olcAccess: to dn.exact="fvd=fripost.org,ou=virtual,dc=fripost,dc=org"
attrs=entry,objectClass,fvd
filter=(&(objectClass=FripostVirtualDomain)(!(objectClass=FripostPendingEntry)))
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd
by users =0 break
olcAccess: to dn.regex="^fvl=[^,]+,fvd=fripost.org,ou=virtual,dc=fripost,dc=org$"
attrs=entry,entryDN,entryUUID,objectClass,fvl,fripostIsStatusActive
filter=(&(objectClass=FripostVirtualUser)(!(objectClass=FripostPendingEntry))(fripostIsStatusActive=TRUE))
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd
by users =0 break
olcAccess: to dn.exact="ou=groups,dc=fripost,dc=org"
attrs=entry,objectClass
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd
by users =0 break
olcAccess: to dn.exact="cn=medlemmar,ou=groups,dc=fripost,dc=org"
+ attrs=entry,entryDN,entryUUID,objectClass,cn,description,member
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd
by users =0 break
olcAccess: to dn.exact="cn=styrelse,ou=groups,dc=fripost,dc=org"
+ attrs=entry,entryDN,entryUUID,objectClass,cn,description,member
by dn.exact="cn=nextcloud,ou=services,dc=fripost,dc=org" tls_ssf=128 =rsd
by users =0 break
#
# TODO: allow users to edit their entry, etc
#
{% endif %}
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Catch-all
#
# * Catch all the breaks above.
# * Deny any access to everyone else.
olcAccess: to dn.subtree="dc=fripost,dc=org"
by dn.children="ou=virtual,dc=fripost,dc=org" +0
by * =0
# vim: set filetype=ldif :