The UDM library should support attribute-based access control (ABAC) for all operations (e.g.: `create`, `modify`, `move`, `rename`, `remove` of objects, creation of reports, license import) performed on UDM objects and its properties. The authorization model must integrate with the Guardian concept, primarily using the `guardianRoles` attribute to determine permissions of the actor. Some requirements: * UDM must enforce ABAC rules on all object-level operations. * All write and read operations must be executed using a privileged LDAP account (e.g. cn=admin). * A bypass mechanism is required for system accounts and components that lack `guardianRoles` (and to avoid cyclic dependencies e.g. Guardian fetching actor information from UDM REST API). * ABAC checks must be enableable per LDAP connection to support system-level operations without restrictions. * The old (JSON based) prototype implementation in the UDM UMC module (See Bug #58113) has to be removed and replaces with this solution. * A domain-specific language (DSL) should be introduced to describe ABAC policies declaratively. As the Guardian concept is abstract, the configuration of policies/rules for all UDM modules would be very large and not maintainable. * The DSL must abstract Guardian internals and offer a simple, maintainable way to define access rules. * UDM should ship with (besides a "Domain Admin" role) three predefined role policies: 1. A Organizational Unit Administrator, which is able to administrate a certain Organizational Unit (without being able to privilege escalation e.g. restrict the access to `guardianRoles`). 2. A Helpdesk Operator, which is able to modify passwords of users in a certain OU. 3. A Linux Client Join operator, which is able to create computers/linux accounts in a certain OU. * The API of UDM must mimic LDAP e.g. to prevent information disclosure Example: a `permissionDenied` exception would reveal the existence of a object, therefor if no read permission exists, a `noObject` exception must be raised. * The implementation should be in a object oriented way and allow re-use. * The implementation must cover UMC-UDM and UDM REST API. * As we don't have third party components under our control, no API changes are possible. However, to keep this in a secure way, the third party extensions must align with the necessary adjustments. They have to be adjusted by App ISVs, PS, etc accordingly. This was a decision from product management. The implementation is the next increment (spike) for the delegative administration feature. Further changes are required to bring it into product maturity. 1) A sketch for the API and the initialization of authorization in a service: ```python # API Sketch class Authorization: def enable(self, get_privileged_connection_callback): … def inject_ldap_connection(self, ldap_connection): … # transforms user LDAP connection into connection with admin powers def is_create_allowed(self, obj): … def is_modify_allowed(self, obj): … def is_rename_allowed(self, obj): … def is_move_allowed(self, obj, target): … def is_remove_allowed(self, obj): … def is_receive_allowed(self, obj): … def filter_search_results(self, results): … def filter_object_properties(self, obj): … # Example usage def init_the_service() lo_admin = univention.admin.uldap.getAdminConnection() # e.g. cache somehwere univention.admin.authorization.Authorization.enable(lambda: lo_admin) # get user connection lo = univention.admin.uldap.access( binddn='uid=ou1admin,cn=users,dc=ucs,dc=test', bindpw='univention', base=ucr['ldap/base'] ) po = position(lo.base) lo = univention.admin.authorization.Authorization.inject_ldap_connection(lo) # extend user connection to have admin powers def main() init_the_service() users = univention.admin.modules.get('users/user') univention.admin.modules.init(lo, po, users) user = users.object(None, lo, po, ) user.create() # Enforcement in Object logic class object(simpleLDAP): def create(self): … if not self.lo.authz.is_create_allowed(self): raise permissionDenied() self.lo.authz_connection.add(self.dn, self.addlist) … ``` +++ This bug was initially created as a clone of Bug #58113 +++
The ABAC authorization concept has been integrated into UDM. The authorization checks are enabled per LDAP connection (and additionally globally per service): It can be enabled in UMC and UDM REST API (not yet in UDM-CLI - we just didn't integrate a UCR variable for it, can possibly just be enabled there as well) via the following UCR variables. `directory/manager/web/delegative-administration/enabled`=true `directory/manager/rest/delegative-administration/enabled`=true Via `directory/manager/rest/delegative-administration/excluded-users/.*` one can specify users which are excluded from these authorization checks, e.g. necessary so that `cn=admin` stil works. TODO: implement the same for 'directory/manager/rest/delegative-administration/excluded-groups/.*' Configuration is still locally and not distributed somewhere. Can be configured via: /usr/share/univention-directory-manager-tools/univention-configure-udm-authorization --store-local prune /usr/share/univention-directory-manager-tools/univention-configure-udm-authorization --store-local create-permissions /usr/share/univention-directory-manager-tools/univention-configure-udm-authorization --store-local create-default-roles A language to describe the policy rules has been implemented: a UDM domain specific language (DSL) following an extended BNF grammar. This is parsed by a LALR (Look-Ahead Left <- Right) parser. The default rules are in: `/usr/share/univention-directory-manager-modules/udm-default-authorization-roles.policy` Currently rules for the same role must be defined in the same `.policy` file. Syntax highlighting for this file (e.g. via vim) can be enabled, see toolshed:`vim/syntax/udm.vim` and `vim/ftdetect/udm.vim` Example: ``` # Domain Administrators access by role="udm:default-roles:domain-administrator" description="Domain Admins are allowed to do anything in the whole domain" to objecttype="*" grant actions="*" grant properties="*" permission="write" ``` Further policies can be activated via: `/usr/share/univention-directory-manager-tools/univention-configure-udm-authorization --store-local create-roles --config /etc/….policy` UDM delivers the following default roles: * Domain Administator (can do anything): udm:default-roles:domain-administrator * Domain User (can read himself): udm:default-roles:domain-user * Self Service Profile view (can write self service properties): udm:default-roles:self-service-profile * Organizational Unit Administrtor (can administate users and groups in a OU): udm:default-roles:organizational-unit-admin (only in combination with the udm:contexts:position context) * Helpdesk Operator (can reset passwords): udm:default-roles:helpdesk-operator * Linux Client Mananger (can create computers/linux accounts): udm:default-roles:linux-ou-client-manager For those users with these roles, LDAP ACL's can be added which grant nothing anymore. By default every of these users are able to read the whole contents of the LDAP directory, without sensitive data such as password hashes: ``` access to * by dn.base="uid=ou1-admin,cn=users,dc=ucs,dc=test" none stop by * +0 break ``` # Implications on UDM usage: UDM is not prepared to handle such a API change, which we did in a backwards compatible manner. Error handling now must be extended everywhere to catch e.g. `permissionDenied` and `noObject`. Authorization checks and object or property filtering needs to be done manually in various places, to not introduce priviledge escalation or information disclosure vulnerabilities. External hooks/modules/syntax classes must implement this manually. The ldap connection instance `lo` provides therefore the new members `authz` (providing authorization methods), `authz_connection` (being a privileged `cn=admin` connection) and other utility functions (which might change in the future). The use of `authz_connection` can be compared with encoding/decoding of data. The `lo.authz_connection` must be used for all operations like `get_schema()`, `add()`, `modify()`, `rename()`, `delete()`, `getPolicies()`, `get()`, `getAttr()`, `search()` and `searchDn()`. The `lo` connection must be used for all other operations, e.g. passing to UDM objects `univention.admin.modules.lookup(lo, …)`, `univention.admin.modules.get("users/user").object(…, lo, …)` or `univention.admin.modules.init(module, lo, …)` Additionally the methods `get()`, `getAttr()`, `search()` and `searchDn()` must be guarded, to filter the results for information the user is not allowed to read! E.g. alternatives are `lo.search_filtered()` and `lo.search_dn_filtered()`. In general it's very important that things raise the same exception signature like LDAP would do, if no permissions exists, otherwise with that information leak it would be possible to find out which objects exsits, especially if the user has control over the used LDAP filter, it can be used to obtain arbitrary domain data. The implementation still has several vulnerabilities: * probably not all endpoints of UMC and UDM REST API covered * various endpoints which offer the possibility to provide an LDAP filter or LDAP search base + scope, which doesn't respond equally to if the object doesn't exists in LDAP * information disclosure in various methods where the behavior differs from LDAP. most known places we e.g. raise `noObject` instead of `permissionDenied`. leftover: whole tree hierarchy must be checked. * information disclosure via specially crafted LDAP DNs mighgt allow this (e.g. `uid = Administrator` is equal to `uid=Administrator`). This is currently safely hanlded. If we switch to real Guardian and evaluate this with rego, it will probably vulnerable. * time based side channel attacks might be possible now. * UDM modules which implement `lookup()` on their own, instead of using the generic variant e.g. computers/ipmanagedclient. * ~~DNS and DHCP objects if access to computers is allowed~~ I don't consider those information secret!? They could also be fetched via a DNS request. * UDM modules with a defect `hasChange()` method * pseudo-properties without LDAP attribute, might be not correctly detected by `hasChange()` * UDM objects which aren't `open()`ed, when the open logic adds state * It's possible to "touch" any object, by sending a modify request without changed properties. * All marked places in the UDM source code matching: `git grep 'information disclosure'` * probably many other things From product management we were not allowed to implement this feature by creating mappings to LDAP ACLs, which would make us safe against all this (and would have been a lot easier to implement). Therefor we have to address them somewhen. Other vulnerabilities are possible in the rules itself. Creating rules requires knowledge about the object structure. To protect against privilege escalation we must prevent write access to (certain values of) properties like users/user or groups/group:password,serviceSpecificPassword,guardianRoles,guardianMemberRoles. Also certain pathes in homeSharePath and other attacks are imaginable. Other wrong role definitions can lead to information loss, e.g.: If the actor user has access to read group XYZ and another actor modificator of groups has not, the groups will be removed. The place of implementation is also kind of wrong: Authorization checks are done very early in modify()/create()/etc to prevent that the _ldap_pre_create/modify() logic, which already does things like moving, creation, can be executed by any arbitraty actor, not having the permissions. But exactly that logic often sets default values, modifies states, executes hooks, etc which is then not covered by the authorization checks. Maybe it would be best to just check the permissions twice.
Overview of commits: univention-s4-connector.yaml 3775a280133a | chore(s4-connector): update advisory univention-s4-connector (16.2.1) d8f18a6b16cd | feat(s4-connector): always set LDAP base when initializing uldap.access() univention-python.yaml d11530bad88e | chore(udm): add advisory univention-python (15.2.1) cbf79206dbb8 | feat(uldap): add optional strict type checking univention-python (15.2.0) dc5a075e1e25 | feat(udm): implement ABAC (attribute-based access control) authorization in UDM 22d22946f2fc | feat(python): add univention.dn module e7243b16dc85 | fix!(univention-python): don't allow access() without a valid base d3be950d34c5 | perf(uldap): compare LDAP base DN correctly but make a lazy comparision first univention-management-console-module-udm.yaml d11530bad88e | chore(udm): add advisory univention-management-console-module-udm (12.2.6) 9f87a2b49e49 | fix(udm-umc): fix error handling in case the LDAP base is not readable univention-management-console-module-udm (12.2.5) 06e2c0866a2f | feat(umc-udm): check if license-import is allowed 5d03d310bfcf | feat(udm-umc): check if permissions for report creation exists a78614db7618 | fix(udm): use priviledged LDAP connection c82b56c9437b | feat(udm-rest): enable ABAC (attribute-based access control) authorization in UDM REST API dc5a075e1e25 | feat(udm): implement ABAC (attribute-based access control) authorization in UDM univention-lib.yaml bb124112316e | feat(lib): set base in uldap.access() univention-lib (11.2.0) bb124112316e | feat(lib): set base in uldap.access() univention-directory-manager-rest.yaml d11530bad88e | chore(udm): add advisory univention-directory-manager-rest (12.2.1) 5d03d310bfcf | feat(udm-umc): check if permissions for report creation exists 90db702381f0 | fix(udm-rest): fix error handling c82b56c9437b | feat(udm-rest): enable ABAC (attribute-based access control) authorization in UDM REST API univention-directory-manager-rest (12.2.0) 53ee0cabf764 | perf(udm-rest): enhance resolving of relations univention-directory-manager-modules.yaml d11530bad88e | chore(udm): add advisory univention-directory-manager-modules (17.2.9) e32875852783 | feat(udm): add further warning about wrong use of univention.uldap.access() within UDM code f9205d390111 | feat(udm): Configure ABAC in UDM using Guardian-like engine and UDM Domain Specific Language 0e3509b603c0 | fix(udm): don't call modify if nothing has been changed a78614db7618 | fix(udm): use priviledged LDAP connection dc5a075e1e25 | feat(udm): implement ABAC (attribute-based access control) authorization in UDM univention-directory-manager-modules (17.2.8) a2406e87ecf7 | feat(udm): add Helpdesk Operator (helpdesk-operator) role e439d797073f | feat(udm): add linux-client-manager role 4bba3e1654ea | fix(udm): users/ldap doesn't have guardianInheritedRoles 8a76fc4c1592 | fix(udm): do nagios cleanup only if nagios option is enabled af6969c0cc36 | fix(udm): don't change the object's groups property when loading guardian roles 419e93e14d95 | feat(udm): adjust size of guardian roles in web UI a21441affa2d | test(udm): implement authorization unittests f9d3121af3f7 | test(udm): implement authorization tests univention-directory-manager-modules (17.2.11) 0d8376907865 | feat(authorization): fetch setting objects with privileged connection univention-directory-manager-modules (17.2.10) 9b94d7b81ae7 | style(udm): transform type comment into type annotations 62998f8405f1 | feat(udm): prevent traceback during upgrade e2ce6daec9dd | style(udm): fix some ruff issues 442217ae7669 | perf(udm): fix RUF015 unnecessary-iterable-allocation-for-first-element 2a72f3074c16 | refactor(udm): remove circular import 056a28fbd921 | style(udm): Drop Python 2 specific code a1ad885b2e4e | docs(uldap): document public API 4ba8d433696f | feat(udm): introduce optional by default deactivated strict type checking univention-authorization.yaml d11530bad88e | chore(udm): add advisory univention-authorization (1.0.0) b6991bdc50c8 | feat(authorization): Implement guardian-like authorization engine univention-ad-connector.yaml 3775a280133a | chore(s4-connector): update advisory univention-ad-connector (16.2.1) 5c34ccb789d8 | feat(ad-connector): always set LDAP base when initializing uldap.access() ucs-test (12.2.22) 4b2eb4080832 | test(ad-connector): fix usage of uldap.access in AD connector tests ucs-test (12.2.21) ce76b57d4bbf | test(ucs-test): fix typo in 32_udm_nonposix_user_actions ucs-test (12.2.19) dfa393b611c3 | test(ucs-test): fix hardcoded use of "Domain Admins" ucs-test (12.2.18) aa51bfd55384 | test(udm-authorization): expect users/ldap with no priviledges to not be able to search for anything 061737636002 | test(udm-authorization): fix creation of initial OU structure 936d6daa9d43 | test(udm-authorization): fix role names ucs-test (12.2.17) a61fe7326956 | test(ucs-test): fix 45_radius/11_service_specific_password.py ucs-test (12.2.16) 01c44e99abdb | test(ucs-test): fix usage of univention.admin.uldap.access ucs-test (12.2.15) af411806ad6d | refactor(udm-authorization): rewrite create-delegated-administration-setup ucs-test (12.2.14) 482085c80156 | test(udm-authorization): hierarchical ou structure 3f7f9b3c37f0 | test(base): rewrite test 45udm-cli.py in pytest 7d014ead27b6 | test(radius): align to error handling 18064223b558 | test(udm-authorization): consider primary group roles f9205d390111 | feat(udm): Configure ABAC in UDM using Guardian-like engine and UDM Domain Specific Language dc5a075e1e25 | feat(udm): implement ABAC (attribute-based access control) authorization in UDM 7681e747fdf3 | test(udm): add authorization tests for helpdesk-operator role e439d797073f | feat(udm): add linux-client-manager role 076c444a8fba | test(udm): add authorization tests f9d3121af3f7 | test(udm): implement authorization tests d23c0d89202f | ci(jenkins): add guardian setup for delegated administration bf06fbd1fba1 | test(ucs-test-authorization): move existing authorization tests into new test section 42_authorization test_simple.yaml a21441affa2d | test(udm): implement authorization unittests test_multiple_positions2.yaml a21441affa2d | test(udm): implement authorization unittests test_multiple_positions.yaml a21441affa2d | test(udm): implement authorization unittests NONE 88a6f2a2e022 | test(upgrade): prune old kernels f2a01ec7cd9d | ci(utils): set directory/mananger/type-checking/strict=true in all instances f83c31c70b3e | test(udm-authorization): new test job for main branch
OK - yaml OK - packages OK - tests
<https://errata.software-univention.de/#/?erratum=5.2x141> <https://errata.software-univention.de/#/?erratum=5.2x142> <https://errata.software-univention.de/#/?erratum=5.2x143> <https://errata.software-univention.de/#/?erratum=5.2x144> <https://errata.software-univention.de/#/?erratum=5.2x145> <https://errata.software-univention.de/#/?erratum=5.2x146> <https://errata.software-univention.de/#/?erratum=5.2x147> <https://errata.software-univention.de/#/?erratum=5.2x148>