Renamed the mariadb, openldap and postgres database

This commit is contained in:
2025-07-12 16:06:13 +02:00
parent e174523fc6
commit 3b03c5171d
84 changed files with 210 additions and 106 deletions

View File

@@ -0,0 +1,38 @@
# LDAP
## Description
Unleash the potential of centralized identity management with OpenLDAP. This powerful directory service provides a robust platform for managing users, groups, and organizational units while ensuring secure, scalable, and efficient authentication and authorization.
## Overview
Deploy OpenLDAP in a Docker environment with support for TLS-secured communication via an NGINX stream proxy. OpenLDAP offers advanced directory management capabilities, including flexible schema definitions, dynamic configuration overlays, and comprehensive query support with LDAP search utilities.
For further setup instructions and advanced configuration details, please refer to the following resources available in this directory:
- [Administration.md](docs/Administration.md)
- [Installation.md](docs/Installation.md)
- [Change_DN.md](docs/Change_DN.md)
## Features
- **Centralized Identity Management:** Maintain a unified repository for all users and groups with robust organizational structures.
- **Flexible Schema Support:** Customize and extend directory schemas to meet diverse organizational requirements.
- **Secure Communications:** Enable TLS encryption for data in transit when accessed through an NGINX reverse proxy.
- **Dynamic Configuration:** Leverage runtime configuration overlays to adjust directory settings without downtime.
- **Comprehensive Query Capabilities:** Utilize LDAP search tools to efficiently query and manage directory data.
- **High Performance and Scalability:** Designed to handle large-scale deployments with rapid lookup and authentication response times.
## Further Resources
- [Bitnami OpenLDAP](https://hub.docker.com/r/bitnami/openldap)
- [phpLDAPadmin Documentation](https://github.com/leenooks/phpLDAPadmin/wiki/Docker-Container)
- [LDAP Account Manager](https://github.com/LDAPAccountManager/docker)
- [RBAC Wikipedia](https://de.wikipedia.org/wiki/Role_Based_Access_Control)
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [veen.world](https://www.veen.world).
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais)
Licensed under [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl).

View File

@@ -0,0 +1,3 @@
# Todos
- User Groups and Roles on the proper way
- Refactor the use of groups as roles in oauth2 proxy

View File

View File

@@ -0,0 +1,11 @@
hostname: "svc-db-openldap" # Hostname of the LDAP Server in the ldap network
network:
name: "svc-db-openldap"
local: True # Activates local network. Necessary for LDIF import routines
docker: True # Activates docker network to allow other docker containers to connect
public: False # Set to true in inventory file if you want to expose the LDAP port to the internet
images:
openldap: "bitnami/openldap:latest"
webinterface: "lam" # The webinterface which should be used. Possible: lam and phpldapadmin
features:
ldap: true

View File

@@ -0,0 +1,72 @@
# Administration
## Configuration
## Load env
To use the following commands firs load the env:
```bash
export $(grep -v '^[[:space:]]*#' ./.env/env \
| sed -E 's/#.*$//; /^[[:space:]]*$/d; s/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]*=[[:space:]]*/=/' \
| xargs)
```
### Show Configuration
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=config'"
```
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=config' -s base '(objectClass=*)'"
```
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=config' -s base '(objectClass=olcModuleList)'"
```
### Databases Overview
```bash
docker exec -it ldap ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config" "(olcDatabase=*)"
```
## Data
### Set Credentials
To execute the following commands set the credentials via:
```bash
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
```bash
docker exec -it ldap bash -c "ldapsearch -LLL -o ldif-wrap=no -x -D \"\$LDAP_ADMIN_DN\" -w \"\$LDAP_ADMIN_PASSWORD\" -b \"\$LDAP_ROOT\"";
```
### 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,$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.ldif"
```

View File

@@ -0,0 +1,133 @@
# Change Distinguished Name (DN) in OpenLDAP Docker
This document provides a step-by-step guide on how to rename the Distinguished Name (DN) from `cn=administrator,dc=flock,dc=town` to `cn=administrator,dc=cymais,dc=cloud` in an **OpenLDAP Docker** environment.
**Reference:** [Conversation Link](https://chatgpt.com/share/67d9a2f7-4e04-800f-9a0f-1673194f276c)
---
## 1. Export the Current Entry
Connect to the OpenLDAP container and export the current entry:
```sh
docker exec -it ldap sh -c 'ldapsearch -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" -b "$LDAP_ROOT"' > all_entries.ldif
```
If your ***LDAP_ADMIN_DN*** and ***LDAP_ROOT*** are not accured pass them via ``--env``.
---
## 2. Modify the LDIF File
Open `all_entries.ldif` and update the DN (`dn:` line) and `dc` attributes.
- Open the file in an editor (`nano`, `vim`, `sed`).
- Replace **all occurrences** of `dc=flock,dc=town` with `dc=cymais,dc=cloud`.
**Using `sed` to modify automatically:**
```sh
sed -i 's/dc=flock,dc=town/dc=cymais,dc=cloud/g' all_entries.ldif
```
**Before:**
```ldif
dn: cn=administrator,dc=flock,dc=town
cn: administrator
objectClass: organizationalRole
objectClass: simpleSecurityObject
userPassword: {SSHA}...
```
**After:**
```ldif
dn: cn=administrator,dc=cymais,dc=cloud
cn: administrator
objectClass: organizationalRole
objectClass: simpleSecurityObject
userPassword: {SSHA}...
```
---
## 3. Delete the Old Entry
### Generate a Recursive Delete LDIF
We need an **LDIF file that deletes all objects** under `dc=flock,dc=town`.
Instead of manually writing an LDIF file, you can use `ldapsearch` and `awk` to generate it dynamically:
```sh
docker exec -it ldap sh -c 'ldapsearch -x -D "cn=administrator,dc=flock,dc=town" -w "$LDAP_ADMIN_PASSWORD" -b "dc=flock,dc=town" dn' | awk "/^dn:/ {print \$2}" | tac > delete_all_dns.txt
```
This creates an **ordered delete list**, starting with child objects before deleting `dc=flock,dc=town`.
---
#### Apply the Recursive Delete
Now apply the generated `delete_all.ldif` to delete all entries **recursively**:
```sh
docker exec -i ldap sh -c '
while read dn; do
ldapdelete -x -D "cn=administrator,dc=flock,dc=town" -w "$LDAP_ADMIN_PASSWORD" "$dn"
done' < delete_all_dns.txt
```
---
#### Verify That Everything Is Deleted
After running the delete command, verify that `dc=flock,dc=town` is empty:
```sh
docker exec -it ldap sh -c 'ldapsearch -x -D "cn=administrator,dc=flock,dc=town" -w "$LDAP_ADMIN_PASSWORD" -b "dc=flock,dc=town"'
```
- ✅ If **no results** are returned, the domain has been deleted successfully.
- ❌ If results still exist, some entries were not removed.
#### Create new_database.ldif
docker exec -i ldap ldapadd -Y EXTERNAL -H ldapi:/// -f /dev/stdin < new_database.ldif
## 4. Add the New Entry
Now, upload the modified `all_entries.ldif`:
```sh
cat all_entries.ldif | docker exec -i ldap sh -c 'ldapadd -x -D "cn=admin,dc=cymais,dc=cloud" -w "$LDAP_ADMIN_PASSWORD"'
```
---
## 5. Update Root DN Configuration
If `cn=administrator` is used as `rootdn`, update the OpenLDAP configuration file (`slapd.conf` or `olcDatabase={1}mdb.ldif` under `cn=config`).
Find:
```ldif
olcRootDN: cn=administrator,dc=flock,dc=town
```
Replace with:
```ldif
olcRootDN: cn=administrator,dc=cymais,dc=cloud
```
Save the change and apply it:
```sh
docker exec -it ldap ldapmodify -Y EXTERNAL -H ldapi:/// -f config_update.ldif
```
---
## 6. Restart OpenLDAP
Restart the OpenLDAP container if necessary:
```sh
docker restart ldap
```
Now, `cn=administrator,dc=cymais,dc=cloud` should be active as the new administrator account.

View File

@@ -0,0 +1,35 @@
# Installation
## MemberOf
```bash
# Activate
ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: /opt/bitnami/openldap/lib/openldap/memberof.so
EOF
# Verify
ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b "cn=module{0},cn=config" olcModuleLoad
ldapadd -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=memberof,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: memberof
olcMemberOfRefInt: TRUE
olcMemberOfDangling: ignore
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
EOF
```
### Verifiy that MemberOf is activated and loaded
```bash
docker exec -it ldap sh -c 'ls -l /opt/bitnami/openldap/lib/openldap/memberof.*'
docker exec -it ldap ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcOverlayConfig)(olcOverlay=memberof))'
```

View File

@@ -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
}

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["user"]["attributes"]["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

@@ -0,0 +1,55 @@
- name: Load memberof module from file in OpenLDAP container
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/01_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
# @todo Remove the following ignore errors when setting up a new server
# Just here because debugging would take to much time
ignore_errors: true
- name: Refint Module Activation for OpenLDAP
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/02_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
register: ldapadd_result
failed_when: ldapadd_result.rc not in [0, 68]
# @todo Remove the following ignore errors when setting up a new server
# Just here because debugging would take to much time
ignore_errors: true
- name: "Import schemas"
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -Y EXTERNAL -H ldapi:/// -f "{{ldif_docker_path}}schema/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 80]
listen:
- "Import schema LDIF files"
- "Import all LDIF files"
loop: "{{ lookup('fileglob', role_path ~ '/templates/ldif/schema/*.j2', wantlist=True) }}"
- name: Refint Overlay Configuration for OpenLDAP
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// -f {{ldif_docker_path}}configuration/03_member_of_configuration.ldif
listen:
- "Import configuration LDIF files"
- "Import all LDIF files"
register: ldapadd_result
failed_when: ldapadd_result.rc not in [0, 68]
# @todo Remove the following ignore errors when setting up a new server
# Just here because debugging would take to much time
ignore_errors: true
- name: "Import users, groups, etc. to LDAP"
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -x -D "{{ldap.dn.administrator.data}}" -w "{{ldap.bind_credential}}" -c -f "{{ldif_docker_path}}data/{{ item | basename | regex_replace('\.j2$', '') }}"
register: ldapadd_result
changed_when: "'adding new entry' in ldapadd_result.stdout"
failed_when: ldapadd_result.rc not in [0, 20, 68]
listen:
- "Import data LDIF files"
- "Import all LDIF files"
loop: "{{ query('fileglob', role_path ~ '/templates/ldif/data/*.j2') | sort }}"

View File

@@ -0,0 +1,22 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Unleash the potential of centralized identity management with our robust LDAP Directory solution, powered by OpenLDAP. Manage users, groups, and schemas securely with extensive customization options and integrated TLS support."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- ldap
- openldap
- docker
- directory
- tls
- identity management
repository: https://s.veen.world/cymais
issue_tracker_url: https://s.veen.world/cymaisissues
documentation: https://s.veen.world/cymais
logo:
class: "fa-solid fa-users"

View File

@@ -0,0 +1,10 @@
credentials:
administrator_password:
description: "Initial password for the LDAP administrator (e.g. cn=admin,dc=example,dc=com)"
algorithm: "sha256"
validation: "^[a-f0-9]{64}$"
administrator_database_password:
description: "Password used internally for the database-backed directory admin"
algorithm: "bcrypt"
validation: "^\\$2[aby]\\$.{56}$"

View File

@@ -0,0 +1,34 @@
- name: Gather all users with their current objectClass list
community.general.ldap_search:
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
dn: "{{ ldap.dn.ou.users }}"
scope: subordinate
filter: "{{ ldap.filters.users.all }}"
attrs:
- dn
- objectClass
- "{{ ldap.user.attributes.id }}"
register: ldap_users_with_classes
- name: Add only missing auxiliary classes
community.general.ldap_attrs:
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
dn: "{{ item.dn }}"
attributes:
objectClass: "{{ missing_auxiliary }}"
state: present
async: 60
poll: 0
loop: "{{ ldap_users_with_classes.results }}"
loop_control:
label: "{{ item.dn }}"
vars:
missing_auxiliary: >-
{{ (ldap.user.objects.auxiliary.values() | list)
| difference(item.objectClass | default([]))
}}
when: missing_auxiliary | length > 0

View File

@@ -0,0 +1,11 @@
- 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)
| sort
}}
notify: "Import {{ folder }} LDIF files"

View File

@@ -0,0 +1,135 @@
---
- name: "include docker-compose role"
include_role:
name: docker-compose
- name: Create {{domains | get_domain(application_id)}}.conf if LDAP is exposed to internet
template:
src: "nginx.stream.conf.j2"
dest: "{{nginx.directories.streams}}{{domains | get_domain(application_id)}}.conf"
notify: restart nginx
when: applications[application_id].network.public | bool
- name: Remove {{domains | get_domain(application_id)}}.conf if LDAP is not exposed to internet
file:
path: "{{ nginx.directories.streams }}{{ domains | get_domain(application_id) }}.conf"
state: absent
when: not applications[application_id].network.public | bool
- name: create docker network for LDAP, so that other applications can access it
docker_network:
name: "{{ applications[application_id].network.name }}"
state: present
ipam_config:
- subnet: "{{ networks.local['svc-db-openldap'].subnet }}"
- meta: flush_handlers
- name: "Wait for LDAP to be available"
wait_for:
host: "127.0.0.1"
port: "{{ ports.localhost.ldap['svc-db-openldap'] }}"
delay: 5
timeout: 120
state: started
- name: "Reset LDAP admin passwords"
include_tasks: reset_admin_passwords.yml
when: applications[application_id].network.local
- name: "create directory {{ldif_host_path}}{{item}}"
file:
path: "{{ldif_host_path}}{{item}}"
state: directory
mode: 0755
loop: "{{ldif_types}}"
- name: "Process all LDIF types"
include_tasks: create_ldif_files.yml
loop:
- configuration
loop_control:
loop_var: folder
- name: flush LDIF handlers
meta: flush_handlers
- name: install python-ldap
community.general.pacman:
name:
- python-ldap
state: present
- name: "Include Nextcloud Schema"
include_tasks: schemas/nextcloud.yml
- name: "Include openssh-lpk Schema"
include_tasks: schemas/openssh_lpk.yml
###############################################################################
# 1) Create the LDAP entry if it does not yet exist
###############################################################################
- name: Ensure LDAP users exist
community.general.ldap_entry:
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: "{{ ldap.user.objects.structural }}"
attributes:
uid: "{{ item.value.username }}"
sn: "{{ item.value.sn | default(item.key) }}"
cn: "{{ item.value.cn | default(item.key) }}"
userPassword: "{SSHA}{{ item.value.password }}"
loginShell: /bin/bash
homeDirectory: "/home/{{ item.key }}"
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 }}"
###############################################################################
# 2) Keep the objectClass list AND the mail attribute up-to-date
###############################################################################
- name: Ensure required objectClass values and mail address are present
community.general.ldap_attrs:
dn: "{{ ldap.user.attributes.id }}={{ item.key }},{{ ldap.dn.ou.users }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
attributes:
objectClass: "{{ ldap.user.objects.structural }}"
mail: "{{ item.value.email }}"
state: exact
async: 60
poll: 0
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: "Ensure container for application roles exists"
community.general.ldap_entry:
dn: "{{ ldap.dn.ou.roles }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: organizationalUnit
attributes:
ou: roles
description: Container for application access profiles
state: present
- name: "Process all LDIF types"
include_tasks: create_ldif_files.yml
loop:
- data
loop_control:
loop_var: folder
- name: "Add Objects to all users"
include_tasks: add_user_objects.yml

View File

@@ -0,0 +1,56 @@
---
# Reset both Database and Configuration Admin passwords in LDAP via LDAPI
- name: "Query available LDAP databases"
shell: |
docker exec {{ applications[application_id].hostname }} \
ldapsearch -Y EXTERNAL -H ldapi:/// -LLL -b cn=config "(olcDatabase=*)" dn
register: ldap_databases
- name: "Determine data backend DN (mdb)"
set_fact:
data_backend_dn: >-
{{ ldap_databases.stdout_lines
| select('search','^dn: olcDatabase=.*mdb')
| map('regex_replace','^dn: ','')
| list
| first }}
- name: "Determine config backend DN"
set_fact:
config_backend_dn: >-
{{ ldap_databases.stdout_lines
| select('search','^dn: olcDatabase=\{[0-9]+\}config,cn=config$')
| map('regex_replace','^dn: ','')
| list
| first }}
- name: "Generate hash for Database Admin password"
shell: |
docker exec {{ applications[application_id].hostname }} \
slappasswd -s "{{ ldap.bind_credential }}"
register: database_admin_pw_hash
- name: "Reset Database Admin password in LDAP (olcRootPW)"
shell: |
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: {{ data_backend_dn }}
changetype: modify
replace: olcRootPW
olcRootPW: {{ database_admin_pw_hash.stdout }}
EOF
- name: "Generate hash for Configuration Admin password"
shell: |
docker exec {{ applications[application_id].hostname }} \
slappasswd -s "{{ applications[application_id].credentials.administrator_password }}"
register: config_admin_pw_hash
- name: "Reset Configuration Admin password in LDAP (olcRootPW)"
shell: |
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
dn: {{ config_backend_dn }}
changetype: modify
replace: olcRootPW
olcRootPW: {{ config_admin_pw_hash.stdout }}
EOF

View File

@@ -0,0 +1,32 @@
- name: "pkgmgr install"
include_role:
name: pkgmgr-install
vars:
package_name: ldapsm
- name: Ensure custom LDAP schema snippet via ldapsm
vars:
schema_name: "nextcloud"
attribute_defs:
- "( 1.3.6.1.4.1.99999.1 NAME '{{ ldap.user.attributes.nextcloud_quota }}' DESC 'Quota for Nextcloud' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )"
objectclass_defs:
- "( 1.3.6.1.4.1.99999.2 NAME 'nextcloudUser' DESC 'Auxiliary class for Nextcloud attributes' AUXILIARY MAY ( {{ ldap.user.attributes.nextcloud_quota }} ) )"
command: >
ldapsm
-s {{ ldap_server_uri }}
-D '{{ ldap_bind_dn }}'
-W '{{ ldap_bind_pw }}'
-n {{ schema_name }}
{% for at in attribute_defs %}
-a "{{ at }}"
{% endfor %}
{% for oc in objectclass_defs %}
-c "{{ oc }}"
{% endfor %}
register: ldapsm_result
changed_when: "'Created schema entry' in ldapsm_result.stdout"
check_mode: no
- name: Show ldapsm output
debug:
var: ldapsm_result.stdout_lines

View File

@@ -0,0 +1,40 @@
- name: Install ldapsm
include_role:
name: pkgmgr-install
vars:
package_name: ldapsm
- name: Ensure OpenSSH-LPK schema via ldapsm
vars:
schema_name: "openssh-lpk"
attribute_defs:
- "( 1.3.6.1.4.1.24552.1.1 NAME '{{ ldap.user.attributes.ssh_public_key }}' DESC 'OpenSSH Public Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
- "( 1.3.6.1.4.1.24552.1.2 NAME 'sshFingerprint' DESC 'OpenSSH Public Key Fingerprint' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
objectclass_defs:
- >-
( 1.3.6.1.4.1.24552.2.1
NAME '{{ ldap.user.objects.auxiliary.ssh_public_key }}'
DESC 'Auxiliary class for OpenSSH public keys'
SUP top
AUXILIARY
MAY ( {{ ldap.user.attributes.ssh_public_key }} $ sshFingerprint ) )
command: >
ldapsm
-s {{ ldap_server_uri }}
-D '{{ ldap_bind_dn }}'
-W '{{ ldap_bind_pw }}'
-n {{ schema_name }}
{% for at in attribute_defs %}
-a "{{ at }}"
{% endfor %}
{% for oc in objectclass_defs %}
-c "{{ oc }}"
{% endfor %}
register: opensshlpk_ldapsm
changed_when: "'Created schema entry' in opensshlpk_ldapsm.stdout"
check_mode: no
- name: Show ldapsm output for openssh-lpk
debug:
var: opensshlpk_ldapsm.stdout_lines

View File

@@ -0,0 +1,28 @@
{% include 'roles/docker-compose/templates/base.yml.j2' %}
application:
image: "{{ applications[application_id].images.openldap }}"
container_name: {{ applications[application_id].hostname }}
{% include 'roles/docker-container/templates/base.yml.j2' %}
{% if applications[application_id].network.public | bool or applications[application_id].network.local | bool %}
ports:
- 127.0.0.1:{{ports.localhost.ldap['svc-db-openldap']}}:{{ldap_docker_port}}
{% endif %}
volumes:
- 'data:/bitnami/openldap'
- '{{ldif_host_path}}:{{ldif_docker_path}}:ro'
healthcheck:
test: >
bash -c '
ldapsearch -x -H ldap://localhost:{{ ldap_docker_port }} \
-D "{{ ldap.dn.administrator.data }}" -w "{{ ldap.bind_credential }}" -b "{{ ldap.dn.root }}" > /dev/null \
&& ldapsearch -Y EXTERNAL -H ldapi:/// \
-b cn=config "(&(objectClass=olcOverlayConfig)(olcOverlay=memberof))" \
| grep "olcOverlay:" | grep -q "memberof"
'
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
data:
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -0,0 +1,26 @@
# @See https://hub.docker.com/r/bitnami/openldap
# GENERAL
## Admin (Data)
LDAP_ADMIN_USERNAME= {{applications[application_id].users.administrator.username}} # LDAP database admin user.
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP database admin password.
## Users
LDAP_USERS= ' ' # Comma separated list of LDAP users to create in the default LDAP tree. Default: user01,user02
LDAP_PASSWORDS= ' ' # Comma separated list of passwords to use for LDAP users. Default: bitnami1,bitnami2
LDAP_ROOT= {{ldap.dn.root}} # LDAP baseDN (or suffix) of the LDAP tree. Default: dc=example,dc=org
## Admin (Config)
LDAP_ADMIN_DN= {{ldap.dn.administrator.data}}
LDAP_CONFIG_ADMIN_ENABLED= yes
LDAP_CONFIG_ADMIN_USERNAME= {{applications[application_id].users.administrator.username}}
LDAP_CONFIG_ADMIN_PASSWORD= {{applications[application_id].credentials.administrator_password}}
# Network
LDAP_PORT_NUMBER= {{ldap_docker_port}} # Route to default port
LDAP_ENABLE_TLS= no # Using nginx proxy for tls
LDAP_LDAPS_PORT_NUMBER= {{ldaps_docker_port}} # Port used for TLS secure traffic. Priviledged port is supported (e.g. 636). Default: 1636 (non privileged port).
# Security
LDAP_ALLOW_ANON_BINDING= no # Allow anonymous bindings to the LDAP server. Default: yes.

View File

@@ -0,0 +1,50 @@
# MemberOf Overlay Configuration for OpenLDAP
#
# This file activates the memberOf module and configures the memberOf overlay,
# which is required by Nextcloud for proper group management.
# @see https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html
# @see https://www.adimian.com/blog/how-to-enable-memberof-using-openldap/
#
# The first section loads the memberof module from the specified path.
# - olcModuleLoad: Specifies that the "memberof" module should be loaded.
# - olcModulePath: Provides the full path to the memberof shared object.
#
# The second section configures the memberOf overlay for the designated database.
# - The DN "olcOverlay={0}memberof,olcDatabase={1}hdb,cn=config" sets up the overlay
# on the database backend (here assumed to be "hdb").
# - olcMemberOfDangling: ignore
# Instructs the overlay to ignore references to non-existent objects.
# - olcMemberOfRefInt: TRUE
# Enables referential integrity so that changes in group membership automatically
# update the user's "memberOf" attribute.
# - olcMemberOfGroupOC: groupOfNames
# Specifies that the overlay applies to groups with the object class "groupOfNames".
# - olcMemberOfMemberAD: member
# Indicates that the group's membership is stored in the "member" attribute.
# - olcMemberOfMemberOfAD: memberOf
# Defines that the overlay will maintain the "memberOf" attribute in user entries.
#
# IMPORTANT: All groups created before enabling this module must be deleted and recreated,
# as the overlay only assigns the "member" attribute when a new group is created.
# @todo Solve the following error:
#fatal: [echoserver]: FAILED! => {"changed": true, "cmd": "docker exec -i ldap ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/ldif/01_member_of_configuration.ldif\n", "delta": "0:00:00.059605", "end": "2025-02-25 12:01:18.218851", "msg": "non-zero return code", "rc": 247, "start": "2025-02-25 12:01:18.159246", "stderr": "SASL/EXTERNAL authentication started\nSASL username: gidNumber=0+uidNumber=1001,cn=peercred,cn=external,cn=auth\nSASL SSF: 0\nldapmodify: modify operation type is missing at line 2, entry \"cn=module,cn=config\"", "stderr_lines": ["SASL/EXTERNAL authentication started", "SASL username: gidNumber=0+uidNumber=1001,cn=peercred,cn=external,cn=auth", "SASL SSF: 0", "ldapmodify: modify operation type is missing at line 2, entry \"cn=module,cn=config\""], "stdout": "", "stdout_lines": []}
dn: cn=module,cn=config
cn: module
objectClass: olcModuleList
olcModuleLoad: memberof
olcModulePath: /opt/bitnami/openldap/lib/openldap/memberof.so
dn: olcOverlay={0}memberof,olcDatabase={1}hdb,cn=config
objectClass: olcConfig
objectClass: olcMemberOf
objectClass: olcOverlayConfig
objectClass: top
olcOverlay: memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf

View File

@@ -0,0 +1,10 @@
# Refint Module Activation for OpenLDAP
#
# This section adds the refint module to the LDAP configuration.
# The refint module ensures referential integrity by automatically updating
# or removing references when objects are renamed or deleted.
#
# In this file, the "olcmoduleload" attribute is used to load the "refint" module.
dn: cn=module{1},cn=config
add: olcmoduleload
olcmoduleload: refint

View File

@@ -0,0 +1,23 @@
# Refint Overlay Configuration for OpenLDAP
#
# This file configures the refint overlay for the specified LDAP database.
#
# The overlay is applied to the database (here using the "hdb" backend) and is
# responsible for maintaining referential integrity.
#
# The attribute "olcRefintAttribute" lists the attributes that will be monitored
# for changes. In this case, changes to the following attributes will be tracked:
# - memberof
# - member
# - manager
# - owner
#
# This ensures that any changes in the LDAP directory (such as deletion or modification
# of an object) automatically update all references to that object, preventing dangling references.
dn: olcOverlay={1}refint,olcDatabase={1}hdb,cn=config
objectClass: olcConfig
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
objectClass: top
olcOverlay: {1}refint
olcRefintAttribute: memberof member manager owner

View File

@@ -0,0 +1,6 @@
# Activates Password hashing in ldap
dn: cn=config
changetype: modify
replace: olcPasswordHash
olcPasswordHash: {SSHA}

View File

@@ -0,0 +1 @@
This folder contains configuration files where a specific logic needs to be applied.

View File

@@ -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 %}
#}

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 %}

View File

@@ -0,0 +1 @@
This folder contains files which are importet via ldapadd without any specific logic

View File

@@ -0,0 +1,6 @@
server {
listen {{ ports.public.ldaps['svc-db-openldap'] }}ssl;
proxy_pass 127.0.0.1:{{ ports.localhost.ldap['svc-db-openldap'] }};
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_credentials.j2' %}
}

View File

@@ -0,0 +1,3 @@
users:
administrator:
username: "administrator"

View File

@@ -0,0 +1,17 @@
application_id: "svc-db-openldap"
# LDAP Variables
ldaps_docker_port: 636
ldap_docker_port: 389
ldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap['svc-db-openldap'] }}"
ldap_hostname: "{{ applications[application_id].hostname }}"
ldap_bind_dn: "{{ ldap.dn.administrator.configuration }}"
ldap_bind_pw: "{{ applications[application_id].credentials.administrator_password }}"
# LDIF Variables
ldif_host_path: "{{docker_compose.directories.volumes}}ldif/"
ldif_docker_path: "/tmp/ldif/"
ldif_types:
- configuration
- data
- schema # Don't know if this is still needed, it's now setup via tasks