How-to: Package and Register a Restricted LDAP Read-Only ACL in UCS

How to Package and Register a Restricted LDAP Read-Only ACL in UCS

This article describes how to create a Debian package that registers a restricted LDAP read-only ACL in Univention Corporate Server (UCS). The example grants a dedicated LDAP account read access to a specific LDAP subtree while preventing access to other branches and denying write operations.


1. Create the package directory

mkdir -p ~/ldap-acl-test/debian
mkdir -p ~/ldap-acl-test/usr/share/ldap-acl-readonly
cd ~/ldap-acl-test

2. Create the ACL file

nano usr/share/ldap-acl-test/65ldap-readonly.acl

Adjust the values to match your environment

Example content:

access to dn.exact="ou=bremen,dc=deadpool,dc=intranet"
    by dn.exact="uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" read
    by * +0 break

access to *
    by dn.exact="uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" none stop
    by * +0 break

The ACL grants the account:

  • Read access just to the ou=bremen,... branch
  • No access to other branches
  • No write permissions

The file name 65ldap-readonly.acl is also the file name that will later be used in the UCR template directory:

/etc/univention/templates/files/etc/ldap/slapd.conf.d/65ldap-readonly.acl

3. Create the join script

nano 65ldap-acl-test.inst

Content:

#!/bin/sh

VERSION=1

. /usr/share/univention-join/joinscripthelper.lib
. /usr/share/univention-lib/ldap.sh

joinscript_init

export UNIVENTION_APP_IDENTIFIER="ldap-acl-test-1.1"

ucs_registerLDAPExtension "$@" \
    --ucsversionstart "5.2-0" \
    --ucsversionend "5.99-0" \
    --acl /usr/share/ldap-acl-readonly/65ldap-readonly.acl || die

joinscript_save_current_version

exit 0

Important:

  • The ACL file is specified with --acl.
  • The path points to the source file installed by the package.
  • --acl and the corresponding file path must be specified together.

Make the script executable:

chmod 755 65ldap-acl-test.inst

4. Create the Debian control file

nano debian/control

Content:

Source: ldap-acl-readonly
Section: univention
Priority: optional
Maintainer: Miro <miro@univention.de>
Build-Depends:
 debhelper-compat (= 13),
 univention-join-dev
Standards-Version: 4.3.0.3

Package: ldap-acl-readonly
Architecture: all
Depends:
 univention-join,
 shell-univention-lib,
 ${misc:Depends}
Description: Restricted LDAP read-only ACL
 Installs a UCS LDAP ACL for a restricted read-only account.

5. Create the Debian rules file

nano debian/rules

Content:

#!/usr/bin/make -f

%:
	dh $@ --with univention-join

Make the file executable:

chmod 755 debian/rules

The --with univention-join option ensures that the join script is correctly processed as a package join script.


6. Create the installation file

nano debian/install

Content:

usr/share/ldap-acl-readonly/65ldap-readonly.acl usr/share/ldap-acl-readonly/

After installing the package, this places the ACL file at:

/usr/share/ldap-acl-test/65ldap-readonly.acl

The final destination directory is managed by the UCS LDAP listener.


7. Create the Debian changelog

Using dch:

dch --create \
    --package ldap-acl-readonly \
    --newversion 1.0-1 \
    "Register restricted LDAP read-only ACL"

Alternatively, create the file manually:

nano debian/changelog
ldap-acl-readonly (1.0-1) UNRELEASED; urgency=medium

  * Register restricted LDAP read-only ACL

 -- root <root@ucs5primary.deadpool.intranet>  Wed, 30 Sep 2026 13:18:32 +0200

8. Build the package

From the package directory:

dpkg-buildpackage -us -uc -b

Output:

root@ucs5primary:~/ldap-acl-test# dpkg-buildpackage -us -uc -b
dpkg-buildpackage: info: source package ldap-acl-readonly
dpkg-buildpackage: info: source version 1.0-1
dpkg-buildpackage: info: source distribution UNRELEASED
dpkg-buildpackage: info: source changed by root <root@ucs5primary.deadpool.intranet>
dpkg-buildpackage: info: host architecture amd64
 dpkg-source --before-build .
 debian/rules clean
dh clean --with univention-join
   dh_clean
 debian/rules build
dh build --with univention-join
   dh_update_autotools_config
   dh_autoreconf
   create-stamp debian/debhelper-build-stamp
 debian/rules binary
dh binary --with univention-join
   dh_testroot
   dh_prep
   dh-univention-join-install
   dh_auto_install --destdir=debian/ldap-acl-readonly/
   dh_install
   dh_installdocs
   dh_installchangelogs
   dh_perl
   dh_link
   dh_strip_nondeterminism
   dh_compress
   dh_fixperms
   dh_missing
   dh_installdeb
   dh_gencontrol
   dh_md5sums
   dh_builddeb
dpkg-deb: building package 'ldap-acl-readonly' in '../ldap-acl-readonly_1.0-1_all.deb'.
 dpkg-genbuildinfo --build=binary -O../ldap-acl-readonly_1.0-1_amd64.buildinfo
 dpkg-genchanges --build=binary -O../ldap-acl-readonly_1.0-1_amd64.changes
dpkg-genchanges: info: binary-only upload (no source code included)
 dpkg-source --after-build .
dpkg-buildpackage: info: binary-only upload (no source included)

The resulting package is located one directory level above:

root@ucs5primary:~/ldap-acl-test# ls -l ~/ldap-acl-readonly_*.deb
-rw-r--r-- 1 root root 1788 Sep 30 13:23 /root/ldap-acl-readonly_1.0-1_all.deb

9. Install the package

dpkg -i ~/ldap-acl-test_*.deb
root@ucs5primary:~/ldap-acl-test# dpkg -i ~/ldap-acl-readonly_1.0-1_all.deb
Selecting previously unselected package ldap-acl-readonly.
(Reading database ... 129297 files and directories currently installed.)
Preparing to unpack .../ldap-acl-readonly_1.0-1_all.deb ...
Unpacking ldap-acl-readonly (1.0-1) ...
Setting up ldap-acl-readonly (1.0-1) ...
Calling joinscript 65ldap-acl-readonly.inst ...
2026-09-30 13:24:17.855868526+02:00 (in joinscript_init)
Object exists: cn=ldapacl,cn=univention,dc=deadpool,dc=intranet
Object created: cn=65ldap-readonly,cn=ldapacl,cn=univention,dc=deadpool,dc=intranet

Object modified: cn=65ldap-readonly,cn=ldapacl,cn=univention,dc=deadpool,dc=intranet

Waiting for activation of the extension object 65ldap-readonly: .......OK
2026-09-30 13:24:41.957207790+02:00 (in joinscript_save_current_version)
Joinscript 65ldap-acl-readonly.inst finished with exitcode 0

Verify that the ACL file has been installed:

ls -l /usr/share/ldap-acl-test/65ldap-readonly.acl

The join script should have been installed by dh-univention-join:

ls -l /usr/lib/univention-install/65ldap-acl-readonly.inst

10. Run the join script correctly

:white_check_mark: Use:

univention-run-join-scripts

11. Verify that the LDAP extension has been registered

udm settings/ldapacl list --filter cn="65ldap-readonly"
DN: cn=65ldap-readonly,cn=ldapacl,cn=univention,dc=deadpool,dc=intranet
  active: TRUE
  appidentifier: ldap-acl-test-1.1
  createTimestamp: 2026-09-30 11:24:19
  creatorsName: cn=admin,dc=deadpool,dc=intranet
  data: QlpoOTFBWSZTWdmTQHoAACnZgEAQUB9AAj4v3mAwALhEQAAAABQADQAACSplP0JoaNEw<skip>
  entryCSN: 20260930112440.191139Z#000000#000#000000
  entryUUID: 39053dda-510d-1041-8a58-cd1d38d3c311
  filename: 65ldap-readonly.acl
  modifiersName: cn=admin,dc=deadpool,dc=intranet
  modifyTimestamp: 2026-09-30 11:24:40
  name: 65ldap-readonly
  package: ldap-acl-readonly
  packageversion: 1.0-1
  ucsversionend: 5.99-0
  ucsversionstart: 5.2-0
  univentionObjectIdentifier: 832e19c1-b916-4ab8-88d3-5ec6bab093cf

Also verify that the file has been placed in the intended UCR template directory:

ls -lah /etc/univention/templates/files/etc/ldap/slapd.conf.d/65ldap-readonly.*
-rw-r--r-- 1 root nogroup 257 Sep 30 13:24 /etc/univention/templates/files/etc/ldap/slapd.conf.d/65ldap-readonly.acl
-rw-r--r-- 1 root nogroup  68 Sep 30 13:24 /etc/univention/templates/files/etc/ldap/slapd.conf.d/65ldap-readonly.acl.info

12. Verify the LDAP configuration

The new acl should be configured in the /etc/ldap/slapd.conf

grep -A6 'ou=bremen,dc=deadpool,dc=intranet' /etc/ldap/slapd.conf
access to dn.exact="ou=bremen,dc=deadpool,dc=intranet"
    by dn.exact="uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" read
    by * +0 break

access to *
    by dn.exact="uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" none stop
    by * +0 break

If not, should be possible to fix that with:

  • univention-run-join-scripts
  • ucr commit /etc/ldap/slapd.conf
  • systemctl restart slapd

Running ucr commit manually is normally not required, but it can be used for verification or after correcting the configuration.


13. Test access

Test the permitted branch:

ldapsearch -x \
-D "uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" \
-W -b "ou=bremen,dc=deadpool,dc=intranet"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <ou=bremen,dc=deadpool,dc=intranet> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# Bremen, deadpool.intranet
dn: ou=Bremen,dc=deadpool,dc=intranet
ou: Bremen
univentionObjectIdentifier: 388014b3-6fc2-4a0c-bc61-3f1baf7acf33
displayName: Bremen
ucsschoolRole: school:school:Bremen
univentionObjectType: container/ou
ucsschoolHomeShareFileServer: cn=Bremen,cn=dc,cn=server,cn=computers,ou=Bremen
 ,dc=deadpool,dc=intranet
ucsschoolClassShareFileServer: cn=Bremen,cn=dc,cn=server,cn=computers,ou=Breme
 n,dc=deadpool,dc=intranet
univentionPolicyReference: cn=dhcp-dns-clear,cn=policies,ou=Bremen,dc=deadpool
 ,dc=intranet
univentionPolicyReference: cn=ou-default-ucr-policy,cn=policies,ou=Bremen,dc=d
 eadpool,dc=intranet
objectClass: univentionObject
objectClass: organizationalUnit
objectClass: top
objectClass: ucsschoolOrganizationalUnit
objectClass: univentionPolicyReference

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Test a branch that is not permitted:

ldapsearch -x \
-D "uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" \
-W -b "cn=users,ou=bremen,dc=deadpool,dc=intranet"
root@ucs5primary:~/ldap-acl-test# ldapsearch -x -D "uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" -W -b "cn=users,ou=bremen,dc=deadpool,dc=intranet"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <cn=users,ou=bremen,dc=deadpool,dc=intranet> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

# numResponses: 1

or

ldapsearch -x \
-D "uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" \
-W -b "dc=deadpool,dc=intranet"
root@ucs5primary:~/ldap-acl-test# ldapsearch -x -D "uid=ldap-readonly,cn=users,dc=deadpool,dc=intranet" -W -b "dc=deadpool,dc=intranet"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=deadpool,dc=intranet> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

Documentation


See also: