From 8cd7379419330ca35bd47313633e10b5c35229a7 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 4 Jul 2025 23:43:38 +0200 Subject: [PATCH] Added draft for nested groups --- roles/docker-ldap/TODO.md | 5 +- roles/docker-ldap/docs/Administration.md | 6 +- .../build_ldap_nested_group_entries.py | 77 +++++++++++++++++++ .../templates/ldif/data/01_rbac_group.ldif.j2 | 30 ++++++++ ...{01_rbac.ldif.j2 => 02_rbac_roles.ldif.j2} | 0 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 roles/docker-ldap/filter_plugins/build_ldap_nested_group_entries.py create mode 100644 roles/docker-ldap/templates/ldif/data/01_rbac_group.ldif.j2 rename roles/docker-ldap/templates/ldif/data/{01_rbac.ldif.j2 => 02_rbac_roles.ldif.j2} (100%) diff --git a/roles/docker-ldap/TODO.md b/roles/docker-ldap/TODO.md index aa52e29d..d36672ca 100644 --- a/roles/docker-ldap/TODO.md +++ b/roles/docker-ldap/TODO.md @@ -1,4 +1,3 @@ # Todos -- Implement auto memberof setup -- Create a Dockerfile (may in an own repository) with memberOf -- Find a better decoupling solution for nextcloud \ No newline at end of file +- User Groups and Roles on the proper way +- Refactor the use of groups as roles in oauth2 proxy \ No newline at end of file diff --git a/roles/docker-ldap/docs/Administration.md b/roles/docker-ldap/docs/Administration.md index 4d156225..bfc7398d 100644 --- a/roles/docker-ldap/docs/Administration.md +++ b/roles/docker-ldap/docs/Administration.md @@ -35,7 +35,11 @@ docker exec -it ldap ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config" "(olcDat To execute the following commands set the credentials via: ```bash -export $(grep -Ev '^(#|$)' .env/env | xargs) +eval $( + grep -v '^\s*#' .env/env \ + | sed -E 's/\s*#.*//' \ + | sed -E 's/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/export \1="\2"/' +) ``` ### Show all Entries diff --git a/roles/docker-ldap/filter_plugins/build_ldap_nested_group_entries.py b/roles/docker-ldap/filter_plugins/build_ldap_nested_group_entries.py new file mode 100644 index 00000000..406c65c7 --- /dev/null +++ b/roles/docker-ldap/filter_plugins/build_ldap_nested_group_entries.py @@ -0,0 +1,77 @@ +def build_ldap_nested_group_entries(applications, users, ldap): + """ + Builds structured LDAP role entries using the global `ldap` configuration. + Supports objectClasses: posixGroup (adds gidNumber, memberUid), groupOfNames (adds member). + Now nests roles under an application-level OU: application-id/role. + """ + + result = {} + + # Base DN components + role_dn_base = ldap["dn"]["ou"]["roles"] + user_dn_base = ldap["dn"]["ou"]["users"] + ldap_user_attr = ldap["user"]["attributes"]["id"] + + # Supported objectClass flavors + flavors = ldap.get("rbac", {}).get("flavors", []) + + for application_id, app_config in applications.items(): + # Compute the DN for the application-level OU + app_ou_dn = f"ou={application_id},{role_dn_base}" + + ou_entry = { + "dn": app_ou_dn, + "objectClass": ["top", "organizationalUnit"], + "ou": application_id, + "description": f"Roles for application {application_id}" + } + result[app_ou_dn] = ou_entry + + # Standard roles with an extra 'administrator' + base_roles = app_config.get("rbac", {}).get("roles", {}) + roles = { + **base_roles, + "administrator": { + "description": "Has full administrative access: manage themes, plugins, settings, and users" + } + } + + group_id = app_config.get("group_id") + + for role_name, role_conf in roles.items(): + # Build CN under the application OU + cn = role_name + dn = f"cn={cn},{app_ou_dn}" + + entry = { + "dn": dn, + "cn": cn, + "description": role_conf.get("description", ""), + "objectClass": ["top"] + flavors, + } + + member_dns = [] + member_uids = [] + for username, user_conf in users.items(): + if role_name in user_conf.get("roles", []): + member_dns.append(f"{ldap_user_attr}={username},{user_dn_base}") + member_uids.append(username) + + if "posixGroup" in flavors: + entry["gidNumber"] = group_id + if member_uids: + entry["memberUid"] = member_uids + + if "groupOfNames" in flavors and member_dns: + entry["member"] = member_dns + + result[dn] = entry + + return result + + +class FilterModule(object): + def filters(self): + return { + "build_ldap_nested_group_entries": build_ldap_nested_group_entries + } diff --git a/roles/docker-ldap/templates/ldif/data/01_rbac_group.ldif.j2 b/roles/docker-ldap/templates/ldif/data/01_rbac_group.ldif.j2 new file mode 100644 index 00000000..6bb0ebd9 --- /dev/null +++ b/roles/docker-ldap/templates/ldif/data/01_rbac_group.ldif.j2 @@ -0,0 +1,30 @@ +{# +@todo: activate +{% for dn, entry in (applications | build_ldap_role_entries(users, ldap)).items() %} + +dn: {{ dn }} +{% for oc in entry.objectClass %} +objectClass: {{ oc }} +{% endfor %} +{% if entry.ou is defined %} +ou: {{ entry.ou }} +{% else %} +cn: {{ entry.cn }} +{% endif %} +{% if entry.gidNumber is defined %} +gidNumber: {{ entry.gidNumber }} +{% endif %} +description: {{ entry.description }} +{% if entry.memberUid is defined %} +{% for uid in entry.memberUid %} +memberUid: {{ uid }} +{% endfor %} +{% endif %} +{% if entry.member is defined %} +{% for m in entry.member %} +member: {{ m }} +{% endfor %} +{% endif %} + +{% endfor %} +#} \ No newline at end of file diff --git a/roles/docker-ldap/templates/ldif/data/01_rbac.ldif.j2 b/roles/docker-ldap/templates/ldif/data/02_rbac_roles.ldif.j2 similarity index 100% rename from roles/docker-ldap/templates/ldif/data/01_rbac.ldif.j2 rename to roles/docker-ldap/templates/ldif/data/02_rbac_roles.ldif.j2