Refactored ldap implementation for ssh keys

This commit is contained in:
2025-06-27 16:41:10 +02:00
parent bb73e948d3
commit 40edaa52ad
22 changed files with 167 additions and 67 deletions

View File

@@ -182,7 +182,7 @@ LDAP_PORT="{{ldap.server.port}}"
LDAP_METHOD=
LDAP_UID={{ldap.attributes.user_id}}
LDAP_BASE="{{ldap.dn.root}}"
LDAP_BIND_DN="{{ldap.dn.administrator}}"
LDAP_BIND_DN="{{ldap.dn.administrator.data}}"
LDAP_AUTH=password
LDAP_PASSWORD="{{ldap.bind_credential}}"
LDAP_ROLE_FIELD=

View File

@@ -161,7 +161,7 @@ run:
- exec: rails r "SiteSetting.ldap_sync_port = {{ ldap.server.port }}"
- exec: rails r "SiteSetting.ldap_encryption = 'simple_tls'"
- exec: rails r "SiteSetting.ldap_base_dn = '{{ ldap.dn.root }}'"
- exec: rails r "SiteSetting.ldap_bind_dn = '{{ ldap.dn.administrator }}'"
- exec: rails r "SiteSetting.ldap_bind_dn = '{{ ldap.dn.administrator.data }}'"
- exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'"
# LDAP additional configuration

View File

@@ -67,7 +67,7 @@ ESPOCRM_CONFIG_LDAP_HOST={{ ldap.server.domain }}
ESPOCRM_CONFIG_LDAP_PORT={{ ldap.server.port }}
# ESPOCRM_CONFIG_LDAP_SECURITY: "", SSL or TLS
ESPOCRM_CONFIG_LDAP_SECURITY={{ ldap.server.security }}
ESPOCRM_CONFIG_LDAP_USERNAME={{ ldap.dn.administrator }}
ESPOCRM_CONFIG_LDAP_USERNAME={{ ldap.dn.administrator.data }}
ESPOCRM_CONFIG_LDAP_PASSWORD={{ ldap.bind_credential }}
ESPOCRM_CONFIG_LDAP_BASE_DN={{ ldap.dn.users }}
ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%)

View File

@@ -109,7 +109,7 @@ DJANGO_SECRET_KEY={{applications[application_id].credentials.django_secret}}
LDAP_ENABLED = True
LDAP_SERVER_URI = "{{ldap.server.uri}}"
LDAP_BIND_DN = "{{ldap.dn.administrator}}"
LDAP_BIND_DN = "{{ldap.dn.administrator.data}}"
LDAP_BIND_PASSWORD = "{{ldap.bind_credential}}"
LDAP_SEARCH_FILTER = "(|(cn={0})(mail={0}))"
LDAP_START_TLS = False

View File

@@ -10,5 +10,5 @@ LAM_CONFIGURATION_DATABASE= files
# LDAP Configuration
LDAP_SERVER= {{ldap.server.domain}} # domain of LDAP database root entry
LDAP_BASE_DN= {{ldap.dn.root}} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ldap.dn.administrator}} # LDAP admin user (set as login user for LAM)
LDAP_USER= {{ldap.dn.administrator.data}} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP admin password

View File

@@ -7,7 +7,7 @@
--host "{{ ldap.server.domain }}" \
--port {{ ldap.server.port }} \
--security-protocol "{{ ldap.server.security | trim or 'unencrypted' }}" \
--bind-dn "{{ ldap.dn.administrator }}" \
--bind-dn "{{ ldap.dn.administrator.data }}" \
--bind-password "{{ ldap.bind_credential }}" \
--user-search-base "{{ ldap.dn.users }}" \
--user-filter "{{ ldap.filters.users.login }}" \
@@ -51,7 +51,7 @@
--host "{{ ldap.server.domain }}" \
--port {{ ldap.server.port }} \
--security-protocol "{{ ldap.server.security | trim or 'unencrypted' }}" \
--bind-dn "{{ ldap.dn.administrator }}" \
--bind-dn "{{ ldap.dn.administrator.data }}" \
--bind-password "{{ ldap.bind_credential }}" \
--user-search-base "{{ ldap.dn.users }}" \
--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))" \

View File

@@ -46,7 +46,7 @@ GITEA__security__INSTALL_LOCK=true # Locks the installation page
# (De)activate OIDC
GITEA__openid__ENABLE_OPENID_SIGNUP={{ applications | is_feature_enabled('oidc',application_id) | lower }}
GITEA__openid__ENABLE_OPENID_SIGNUP={{ applications | is_feature_enabled('oidc',application_id) | lower }}
GITEA__openid__ENABLE_OPENID_SIGNIN={{ applications | is_feature_enabled('oidc',application_id) | lower }}
{% if applications | is_feature_enabled('oidc',application_id) or applications | is_feature_enabled('ldap',application_id) %}

View File

@@ -2043,7 +2043,7 @@
"{{ldap.attributes.user_id}}"
],
"bindDn": [
"{{ldap.dn.administrator}}"
"{{ldap.dn.administrator.data}}"
],
"lastSync": [
"1737578007"

View File

@@ -10,5 +10,5 @@ LAM_CONFIGURATION_DATABASE= files
# LDAP Configuration
LDAP_SERVER= {{ldap.server.domain}} # domain of LDAP database root entry
LDAP_BASE_DN= {{ldap.dn.root}} # LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_USER= {{ldap.dn.administrator}} # LDAP admin user (set as login user for LAM)
LDAP_USER= {{ldap.dn.administrator.data}} # LDAP admin user (set as login user for LAM)
LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP admin password

View File

@@ -2,6 +2,15 @@
## 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'"

View File

@@ -45,7 +45,7 @@
- name: "Import users, groups, etc. to LDAP"
shell: >
docker exec -i {{ applications[application_id].hostname }} ldapadd -x -D "{{ldap.dn.administrator}}" -w "{{ldap.bind_credential}}" -c -f "{{ldif_docker_path}}data/{{ item | basename | regex_replace('\.j2$', '') }}"
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]

View File

@@ -20,6 +20,3 @@ galaxy_info:
documentation: https://s.veen.world/cymais
logo:
class: "fa-solid fa-users"
#run_after:
# - "0"
dependencies: []

View File

@@ -1,22 +1,32 @@
- name: "1) Gather all existing user DNs"
- name: Gather all users with their current objectClass list
community.general.ldap_search:
server_uri: "{{ ldap.server.uri }}"
bind_dn: "{{ ldap.dn.administrator }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
base: "{{ ldap.dn.users }}"
dn: "{{ ldap.dn.users }}"
scope: subordinate
filter: "{{ ldap.filters.users.all }}"
attributes: ["dn"]
register: ldap_existing_users
attrs:
- dn
- objectClass
- "{{ ldap.attributes.user_id }}"
register: ldap_users_with_classes
- name: "2) Update each existing user with all user_objects"
- name: Add only missing auxiliary classes
community.general.ldap_attrs:
server_uri: "{{ ldap.server.uri }}"
bind_dn: "{{ ldap.dn.administrator }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
dn: "{{ item.dn }}"
attributes:
objectClass: "{{ ldap.user_objects }}"
state: exact
loop: "{{ ldap_existing_users.entries }}"
objectClass: "{{ missing_auxiliary }}"
state: present
loop: "{{ ldap_users_with_classes.results }}"
loop_control:
label: "{{ item.dn }}"
vars:
missing_auxiliary: >-
{{ ldap.user_objects.auxiliary
| difference(item.objectClass | default([]))
}}
when: missing_auxiliary | length > 0

View File

@@ -49,11 +49,10 @@
state: present
- name: "Include Nextcloud Schema"
include_tasks: create_nextcloud_schema.yml
vars:
ldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
ldap_bind_dn: "cn={{ applications[application_id].users.administrator.username }},cn=config"
ldap_bind_pw: "{{ applications[application_id].credentials.administrator_password }}"
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
@@ -61,10 +60,10 @@
- name: Ensure LDAP users exist
community.general.ldap_entry:
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: "{{ ldap.user_objects }}"
objectClass: "{{ ldap.user_objects.structural }}"
attributes:
uid: "{{ item.key }}" # {{ ldap.attributes.user_id }} can't be used as key here, dynamic key generation isn't possible
sn: "{{ item.value.sn | default(item.key) }}"
@@ -85,13 +84,13 @@
- name: Ensure required objectClass values and mail address are present
community.general.ldap_attrs:
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
attributes:
objectClass: "{{ ldap.user_objects }}"
objectClass: "{{ ldap.user_objects.structural }}"
mail: "{{ item.value.email }}"
state: exact # exact is safest for single-valued attributes
state: exact
loop: "{{ users | dict2items }}"
loop_control:
label: "{{ item.key }}"
@@ -99,8 +98,8 @@
- name: "Ensure container for application roles exists"
community.general.ldap_entry:
dn: "{{ ldap.dn.application_roles }}"
server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
bind_dn: "{{ ldap.dn.administrator }}"
server_uri: "{{ ldap_server_uri }}"
bind_dn: "{{ ldap.dn.administrator.data }}"
bind_pw: "{{ ldap.bind_credential }}"
objectClass: organizationalUnit
attributes:

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 'sshPublicKey' 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 'ldapPublicKey'
DESC 'Auxiliary class for OpenSSH public keys'
SUP top
AUXILIARY
MAY ( sshPublicKey $ 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

@@ -17,7 +17,7 @@ services:
test: >
bash -c '
ldapsearch -x -H ldap://localhost:{{ ldap_docker_port }} \
-D "{{ ldap.dn.administrator }}" -w "{{ ldap.bind_credential }}" -b "{{ ldap.dn.root }}" > /dev/null \
-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"

View File

@@ -12,7 +12,7 @@ LDAP_PASSWORDS= ' ' # Comma separated li
LDAP_ROOT= {{ldap.dn.root}} # LDAP baseDN (or suffix) of the LDAP tree. Default: dc=example,dc=org
## Admin
LDAP_ADMIN_DN= {{ldap.dn.administrator}}
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}}

View File

@@ -1,10 +1,17 @@
application_id: "ldap"
ldaps_docker_port: 636
ldap_docker_port: 389
ldif_host_path: "{{docker_compose.directories.volumes}}ldif/"
ldif_docker_path: "/tmp/ldif/"
ldap.dn.application_roles: "ou=application_roles,{{ldap.dn.root}}"
application_id: "ldap"
# LDAP Variables
ldaps_docker_port: 636
ldap_docker_port: 389
ldap_server_uri: "ldap://127.0.0.1:{{ ports.localhost.ldap.ldap }}"
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
- schema # Don't know if this is still needed, it's now setup via tasks

View File

@@ -67,7 +67,7 @@ plugin_configuration:
-
appid: "user_ldap"
configkey: "s01ldap_dn"
configvalue: "{{ldap.dn.administrator}}"
configvalue: "{{ldap.dn.administrator.data}}"
-
appid: "user_ldap"
configkey: "s01ldap_email_attr"

View File

@@ -2,13 +2,13 @@ openproject_ldap:
name: "{{ primary_domain }}" # Display name for the LDAP connection in OpenProject
host: "{{ ldap.server.domain }}" # LDAP server address
port: "{{ ldap.server.port }}" # LDAP server port (typically 389 or 636)
account: "{{ ldap.dn.administrator }}" # Bind DN (used for authentication)
account: "{{ ldap.dn.administrator.data }}" # Bind DN (used for authentication)
account_password: "{{ ldap.bind_credential }}" # Bind password
base_dn: "{{ ldap.dn.users }}" # Base DN for user search
attr_login: "{{ ldap.attributes.user_id }}" # LDAP attribute used for login
attr_firstname: "givenName" # LDAP attribute for first name
attr_lastname: "sn" # LDAP attribute for last name
attr_mail: "mail" # LDAP attribute for email
attr_lastname: "{{ ldap.attributes.lastname }}" # LDAP attribute for last name
attr_mail: "{{ ldap.attributes.mail }}" # LDAP attribute for email
attr_admin: "{{ openproject_filters.administrators }}" # Optional: LDAP attribute for admin group (leave empty if unused)
onthefly_register: true # Automatically create users on first login
tls_mode: 0 # 0 = No TLS, 1 = TLS, 2 = STARTTLS