Optimized RBAC via LDAP

This commit is contained in:
2025-07-04 08:03:27 +02:00
parent a9f55579a2
commit ee0561db72
25 changed files with 316 additions and 111 deletions

View File

View File

@@ -46,10 +46,23 @@ docker exec -it ldap bash -c "ldapsearch -LLL -o ldif-wrap=no -x -D \"\$LDAP_ADM
### Delete Groups and Subgroup
To delete the group inclusive all subgroups use:
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -o ldif-wrap=no -x -D \"\$LDAP_ADMIN_DN\" -w \"\$LDAP_ADMIN_PASSWORD\" -b \"ou=applications,ou=groups,\$LDAP_ROOT\" dn | sed -n 's/^dn: //p' | tac | while read -r dn; do echo \"Deleting \$dn\"; ldapdelete -x -D \"\$LDAP_ADMIN_DN\" -w \"\$LDAP_ADMIN_PASSWORD\" \"\$dn\"; done"
# Works
docker exec -it ldap \
ldapdelete -x \
-D "$LDAP_ADMIN_DN" \
-w "$LDAP_ADMIN_PASSWORD" \
-r \
"ou=groups,dc=veen,dc=world"
"ou=groups,$LDAP_ROOT"
```
## Import RBAC
```bash
docker exec -i ldap \
ldapadd -x \
-D "$LDAP_ADMIN_DN" \
-w "$LDAP_ADMIN_PASSWORD" \
-c \
-f "/tmp/ldif/data/01_rbac_roles.ldif"
```

View File

@@ -0,0 +1,64 @@
def build_ldap_role_entries(applications, users, ldap):
"""
Builds structured LDAP role entries using the global `ldap` configuration.
Supports objectClasses: posixGroup (adds gidNumber, memberUid), groupOfNames (adds member).
"""
result = {}
for application_id, application_config in applications.items():
base_roles = application_config.get("rbac", {}).get("roles", {})
roles = {
**base_roles,
"administrator": {
"description": "Has full administrative access: manage themes, plugins, settings, and users"
}
}
group_id = application_config.get("group_id")
user_dn_base = ldap["dn"]["ou"]["users"]
ldap_user_attr = ldap["attributes"]["user_id"]
role_dn_base = ldap["dn"]["ou"]["roles"]
flavors = ldap.get("rbac", {}).get("flavors", [])
for role_name, role_conf in roles.items():
group_cn = f"{application_id}-{role_name}"
dn = f"cn={group_cn},{role_dn_base}"
entry = {
"dn": dn,
"cn": group_cn,
"description": role_conf.get("description", ""),
"objectClass": ["top"] + flavors,
}
# Initialize member lists
member_dns = []
member_uids = []
for username, user_config in users.items():
if role_name in user_config.get("roles", []):
user_dn = f"{ldap_user_attr}={username},{user_dn_base}"
member_dns.append(user_dn)
member_uids.append(username)
# Add gidNumber for posixGroup
if "posixGroup" in flavors:
entry["gidNumber"] = group_id
if member_uids:
entry["memberUid"] = member_uids
# Add members for groupOfNames
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_role_entries": build_ldap_role_entries
}

View File

@@ -52,4 +52,4 @@
listen:
- "Import data LDIF files"
- "Import all LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/data/*.j2', wantlist=True) }}"
loop: "{{ query('fileglob', role_path ~ '/templates/ldif/data/*.j2') | sort }}"

View File

@@ -21,6 +21,8 @@
attributes:
objectClass: "{{ missing_auxiliary }}"
state: present
async: 60
poll: 0
loop: "{{ ldap_users_with_classes.results }}"
loop_control:
label: "{{ item.dn }}"

View File

@@ -1,9 +1,11 @@
# In own task file for easier looping
- name: "Create LDIF files at {{ ldif_host_path }}{{ folder }}"
template:
src: "{{ item }}"
dest: "{{ ldif_host_path }}{{ folder }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
mode: '770'
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/' ~ folder ~ '/*.j2', wantlist=True) }}"
loop: >-
{{
lookup('fileglob', role_path ~ '/templates/ldif/' ~ folder ~ '/*.j2', wantlist=True)
| sort
}}
notify: "Import {{ folder }} LDIF files"

View File

@@ -78,6 +78,8 @@
uidNumber: "{{ item.value.uid | int }}"
gidNumber: "{{ item.value.gid | int }}"
state: present # ↳ creates but never updates
async: 60
poll: 0
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
@@ -95,6 +97,8 @@
objectClass: "{{ ldap.user_objects.structural }}"
mail: "{{ item.value.email }}"
state: exact
async: 60
poll: 0
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"

View File

@@ -1,30 +0,0 @@
{%- for application_id, application_config in applications.items() %}
{%- set base_roles = application_config.rbac.roles | default({}) %}
{%- set roles = base_roles | combine({
'administrator': {
'description': 'Has full administrative access: manage themes, plugins, settings, and users'
}
})
%}
{%- for role_name, role_conf in roles.items() %}
dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }}
objectClass: top
objectClass: organizationalRole
objectClass: posixGroup
gidNumber: {{ application_config['group_id'] }}
cn: {{ application_id }}-{{ role_name }}
description: {{ role_conf.description }}
{%- for username, user_config in users.items() %}
{%- set user_roles = user_config.roles | default([]) %}
{%- if role_name in user_roles %}
dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }}
changetype: modify
add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.ou.users }}
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endfor %}

View File

@@ -0,0 +1,23 @@
{% for dn, entry in (applications | build_ldap_role_entries(users, ldap)).items() %}
dn: {{ dn }}
{% for oc in entry.objectClass %}
objectClass: {{ oc }}
{% endfor %}
{% if entry.gidNumber is defined %}
gidNumber: {{ entry.gidNumber }}
{% endif %}
cn: {{ entry.cn }}
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 %}