Added gid to applications to make them posix group ldap compatible

This commit is contained in:
Kevin Veen-Birkenbach 2025-07-03 23:51:14 +02:00
parent a93e1520d4
commit 56b3f854c5
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
10 changed files with 141 additions and 27 deletions

View File

@ -31,7 +31,5 @@ install: build
@echo "⚙️ Install complete." @echo "⚙️ Install complete."
test: test:
@echo "🧪 Running Unit Tests..." @echo "🧪 Running Tests..."
python -m unittest discover -s tests/unit python -m unittest discover -s tests
@echo "🔬 Running Integration Tests..."
python -m unittest discover -s tests/integration

0
__init__.py Normal file
View File

View File

@ -6,6 +6,11 @@ import yaml
import sys import sys
from pathlib import Path from pathlib import Path
plugin_path = Path(__file__).resolve().parent / ".." / "lookup_plugins"
sys.path.insert(0, str(plugin_path))
from application_gid import LookupModule
def load_yaml_file(path): def load_yaml_file(path):
"""Load a YAML file if it exists, otherwise return an empty dict.""" """Load a YAML file if it exists, otherwise return an empty dict."""
if not path.exists(): if not path.exists():
@ -37,6 +42,7 @@ def main():
# Initialize result structure # Initialize result structure
result = {"defaults_applications": {}} result = {"defaults_applications": {}}
gid_lookup = LookupModule()
# Process each role for application configs # Process each role for application configs
for role_dir in sorted(roles_dir.iterdir()): for role_dir in sorted(roles_dir.iterdir()):
role_name = role_dir.name role_name = role_dir.name
@ -67,6 +73,12 @@ def main():
config_data = load_yaml_file(config_file) config_data = load_yaml_file(config_file)
if config_data: if config_data:
try:
gid_number = gid_lookup.run([application_id], roles_dir=str(roles_dir))[0]
except Exception as e:
print(f"Warning: failed to determine gid for '{application_id}': {e}", file=sys.stderr)
sys.exit(1)
config_data["group_id"] = gid_number
result["defaults_applications"][application_id] = config_data result["defaults_applications"][application_id] = config_data
users_meta_file = role_dir / "meta" / "users.yml" users_meta_file = role_dir / "meta" / "users.yml"
transformed_users = {} transformed_users = {}

View File

@ -0,0 +1,42 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import yaml
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
application_id = terms[0]
base_gid = kwargs.get('base_gid', 10000)
roles_dir = kwargs.get('roles_dir', 'roles')
if not os.path.isdir(roles_dir):
raise AnsibleError(f"Roles directory '{roles_dir}' not found")
matched_roles = []
for root, dirs, files in os.walk(roles_dir):
if os.path.basename(root) == "vars" and "main.yml" in files:
vars_path = os.path.join(root, "main.yml")
try:
with open(vars_path, 'r') as f:
data = yaml.safe_load(f) or {}
app_id = data.get('application_id')
if app_id:
matched_roles.append((app_id, vars_path))
except Exception as e:
raise AnsibleError(f"Error parsing {vars_path}: {e}")
# sort alphabetically by application_id
sorted_ids = sorted(app_id for app_id, _ in matched_roles)
try:
index = sorted_ids.index(application_id)
except ValueError:
raise AnsibleError(f"Application ID '{application_id}' not found in any role")
return [base_gid + index]

View File

@ -46,6 +46,10 @@ docker exec -it ldap bash -c "ldapsearch -LLL -o ldif-wrap=no -x -D \"\$LDAP_ADM
### Delete Groups and Subgroup ### Delete Groups and Subgroup
To delete the group inclusive all subgroups use: To delete the group inclusive all subgroups use:
```bash ```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" docker exec -it ldap \
ldapdelete -x \
-D "$LDAP_ADMIN_DN" \
-w "$LDAP_ADMIN_PASSWORD" \
-r \
"ou=groups,dc=veen,dc=world"
``` ```

View File

@ -1,34 +1,30 @@
{% for application_id, application_config in applications.items() %} {%- for application_id, application_config in applications.items() %}
{%- set base_roles = application_config.rbac.roles | default({}) %}
{# 1. Build up roles dict, defaulting to {} if rbac oder roles fehlt, then ensure administrator immer dabei ist #} {%- set roles = base_roles | combine({
{% set base_roles = application_config.rbac.roles | default({}) %}
{% set roles = base_roles | combine({
'administrator': { 'administrator': {
'description': 'Has full administrative access: manage themes, plugins, settings, and users' 'description': 'Has full administrative access: manage themes, plugins, settings, and users'
} }
}) })
%} %}
{# 2. Emit role definitions #} {%- for role_name, role_conf in roles.items() %}
{% for role_name, role_conf in roles.items() %}
dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }} dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }}
objectClass: top objectClass: top
objectClass: organizationalRole objectClass: organizationalRole
objectClass: posixGroup
gidNumber: {{ application_config['group_id'] }}
cn: {{ application_id }}-{{ role_name }} cn: {{ application_id }}-{{ role_name }}
description: {{ role_conf.description }} description: {{ role_conf.description }}
{# 3. Assign only if user has that role #} {%- for username, user_config in users.items() %}
{% for username, user_config in users.items() %} {%- set user_roles = user_config.roles | default([]) %}
{% set user_roles = user_config.roles | default([]) %} {%- if role_name in user_roles %}
{% if role_name in user_roles %}
dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }} dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }}
changetype: modify changetype: modify
add: roleOccupant add: roleOccupant
roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.ou.users }} roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.ou.users }}
{% endif %} {%- endif %}
{% endfor %} {%- endfor %}
{%- endfor %}
{% endfor %} {%- endfor %}
{% endfor %}

0
tests/__init__.py Normal file
View File

View File

0
tests/unit/__init__.py Normal file
View File

View File

@ -0,0 +1,62 @@
import os
import sys
import tempfile
import shutil
import unittest
import yaml
dir_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../../lookup_plugins')
)
sys.path.insert(0, dir_path)
from application_gid import LookupModule
class TestApplicationGidLookup(unittest.TestCase):
def setUp(self):
# Create a temporary roles directory
self.temp_dir = tempfile.mkdtemp()
self.roles_dir = os.path.join(self.temp_dir, "roles")
os.mkdir(self.roles_dir)
# Define mock application_ids
self.applications = {
"nextcloud": "docker-nextcloud",
"moodle": "docker-moodle",
"wordpress": "docker-wordpress",
"taiga": "docker-taiga"
}
# Create fake role dirs and vars/main.yml
for app_id, dirname in self.applications.items():
role_path = os.path.join(self.roles_dir, dirname, "vars")
os.makedirs(role_path)
with open(os.path.join(role_path, "main.yml"), "w") as f:
yaml.dump({"application_id": app_id}, f)
self.lookup = LookupModule()
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_gid_lookup(self):
# The sorted application_ids: [moodle, nextcloud, taiga, wordpress]
expected_order = ["moodle", "nextcloud", "taiga", "wordpress"]
for i, app_id in enumerate(expected_order):
result = self.lookup.run([app_id], roles_dir=self.roles_dir)
self.assertEqual(result, [10000 + i])
def test_custom_base_gid(self):
result = self.lookup.run(["taiga"], roles_dir=self.roles_dir, base_gid=20000)
self.assertEqual(result, [20002]) # 2nd index in sorted list
def test_application_id_not_found(self):
with self.assertRaises(Exception) as context:
self.lookup.run(["unknownapp"], roles_dir=self.roles_dir)
self.assertIn("Application ID 'unknownapp' not found", str(context.exception))
if __name__ == '__main__':
unittest.main()