Following my post on OpenLDAP under Ubuntu Linux Lucid Lynx 10.04, I'd like to address a question submitted as a comment submitted by "raerek":
I plan to set up things like this:
-Group A: members can change the password of anyone, except the password of other Group A members.
-Group B: members can change their own passwords, and the passwords of Group C members.
-Group C: members can only change their own password.
Can you outline how to write the "olcAccess: to" lines - If I do not have to, I would not like to set up three OU-s - I'd rather have everyone in the ou=people.
While I cannot guarantee a response to every question, this one caught my interest - especially due to some of the well-advised reasons I'm guessing are behind the stated requirements.
The "Group A" requirement in particular looked rather complicated when I first looked at this, as the "... anyone, except ..." would normally be addressed with compound conditionals - and OpenLDAP doesn't provide for this - at least not as many would expect. As with many technical issues, the best approach here seems to be in simplifying the functional requirements:
- Anyone can change their own password.
- Is specified by both the original "Group A" and "Group B" requirements, and completely satisfies the original "Group C" requirement.
- "Anyone" should not necessarily apply to "service" or "machine" accounts, unless such an account is specifically known or required to change its own password.
- "Group A" members cannot change the passwords of other "Group A" members.
- "Group A" members can change the passwords of everyone else (besides other "Group A" members, as listed above in #2).
- "Group B" members can change the passwords of "Group C" members.
- Unless otherwise specified above, deny any other access to passwords, except for accounts to read their own password (probably not always even necessary) and for anonymous (an account before authenticated) to authenticate against.
Here is my entire proposed solution, with notes to follow. First, an LDIF export of a setup to demonstrate the example:
dn: dc=example,dc=com dc: Example description: LDAP Example o: Example Organization objectclass: top objectclass: dcObject objectclass: organization dn: ou=groups,dc=example,dc=com objectclass: organizationalUnit ou: groups dn: cn=groupA,ou=groups,dc=example,dc=com cn: groupA description: members can change the password of anyone, except the password of other Group A members. member: cn=groupOfNamesPlaceHolder,ou=groups,dc=example,dc=com member: cn=groupA_user1,ou=people,dc=example,dc=com member: cn=groupA_user2,ou=people,dc=example,dc=com objectclass: groupOfNames dn: cn=groupB,ou=groups,dc=example,dc=com cn: groupB description: members can change their own passwords, and the passwords of Gr oup C members. member: cn=groupOfNamesPlaceHolder,ou=groups,dc=example,dc=com member: cn=groupB_user1,ou=people,dc=example,dc=com member: cn=groupB_user2,ou=people,dc=example,dc=com objectclass: groupOfNames dn: cn=groupC,ou=groups,dc=example,dc=com cn: groupC description: members can only change their own password. member: cn=groupOfNamesPlaceHolder,ou=groups,dc=example,dc=com member: cn=groupC_user1,ou=people,dc=example,dc=com member: cn=groupC_user2,ou=people,dc=example,dc=com objectclass: groupOfNames dn: cn=groupOfNamesPlaceHolder,ou=groups,dc=example,dc=com cn: groupOfNamesPlaceHolder objectclass: applicationProcess dn: ou=people,dc=example,dc=com objectclass: organizationalUnit ou: people dn: cn=groupA_user1,ou=people,dc=example,dc=com cn: groupA_user1 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret dn: cn=groupA_user2,ou=people,dc=example,dc=com cn: groupA_user2 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret dn: cn=groupB_user1,ou=people,dc=example,dc=com cn: groupB_user1 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret dn: cn=groupB_user2,ou=people,dc=example,dc=com cn: groupB_user2 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret dn: cn=groupC_user1,ou=people,dc=example,dc=com cn: groupC_user1 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret dn: cn=groupC_user2,ou=people,dc=example,dc=com cn: groupC_user2 objectclass: inetOrgPerson objectclass: simpleSecurityObject sn: Test userpassword: secret
Note that the userpassword
's are in clear text for the purposes of this example.
They should normally be stored using a cryptographic hash such as salted SHA (SSHA) for proper security.
Next are the olcAccess
rules:
# Req. #1 - Any user in the people OU can change their own password. olcaccess: to dn.subtree="ou=people,dc=example,dc=com" attrs=userPassword by self write by * break # Req. #2 - "Group A" members cannot change the passwords of other "Group A" members. olcaccess: to filter=(memberof=cn=groupA,ou=groups,dc=example,dc=com) attrs=userPassword by group.exact="cn=groupA,ou=groups,dc=example,dc=com" none by * break # Req. #3 - "Group A" members can change the passwords of everyone else. olcaccess: to attrs=userPassword by group.exact="cn=groupA,ou=groups,dc=example,dc=com" write by * break # Req. #4 - "Group B" members can change the passwords of "Group C" members. olcaccess: to filter=(memberof=cn=groupC,ou=groups,dc=example,dc=com) attrs=userPassword by group.exact="cn=groupB,ou=groups,dc=example,dc=com" write by * break # Req. #5 - Unless otherwise specified above, deny any other access to passwords, # except for accounts to read their own password (probably not always even necessary) # and for anonymous (an account before authenticated) to authenticate against. olcaccess: to attrs=userPassword by self read by anonymous auth by * none
First, note that this requires use of the "memberof" overlay,
as detailed in section "12.8. Reverse Group Membership Maintenance" in the OpenLDAP Administrator's Guide.
Without it, the "memberof
" attributes will not exist, and the required filters will not work.
Also note that as provided by the current version of the overlay, the "memberof
" attributes are stored rather than simply calculated when requested (probably for performance reasons), and are only updated when the entry is involved in a modification of group membership.
As such, if the overlay is added to a database with existing populated groups, the groups will likely have to be re-populated for the "memberof
" attribute to reflect the current and expected results.
In testing, any update to the "member
" attribute on a group - even only the addition or removal of one member - will cause the overlay to update the "memberof
" attribute on all other members in the group.
Also related to performance, make sure that "memberof
" is indexed after enabling the overlay.
Next, note that most rules are terminated by a "by * break
" clause.
This allows following rules to be considered, without first terminating with the "by * none
" clause that is implicitly added at the end of every rule,
as detailed in section "8.2.4. Access Control Evaluation" in the OpenLDAP Administrator's Guide.
No work is complete without a good test script. This is also a good reference for how to change account passwords using ldapmodify. Here is the test source as a bash shell script:
#!/bin/bash # Mark A. Ziesemer, http://www.ziesemer.com # 2010-06-06 _cpw(){ # "Change PassWord" local bindUser=$1 local bindPW=$2 local changeUser=$3 local newPW=$4 declare -i expected=$5 # optional, defaults to 0 declare -i result=0 echo "% _cpw $*" echo " dn: $changeUser changetype: modify replace: userpassword userpassword: $newPW" | ldapmodify -D "$bindUser" -xw "$bindPW" || result=$? if [ $result -ne $expected ]; then echo -e "\t* Test FAILED, exit code: $result" if [ $result -gt 0 ]; then return $result; fi return 1 fi echo -e "\t* Test passed, exit code: $result" return 0 } _testTitle(){ echo -e "\n*** $1\n" } _defSuffix="dc=example,dc=com" _peopleOU="ou=people,${_defSuffix}" _groupA_user1="cn=groupA_user1,${_peopleOU}" _groupA_user2="cn=groupA_user2,${_peopleOU}" _groupB_user1="cn=groupB_user1,${_peopleOU}" _groupB_user2="cn=groupB_user2,${_peopleOU}" _groupC_user1="cn=groupC_user1,${_peopleOU}" _groupC_user2="cn=groupC_user2,${_peopleOU}" _testTitle "Testing requirement #1: Anyone can change their own password." _cpw "${_groupA_user1}" "secret" "${_groupA_user1}" "secret1" _cpw "${_groupA_user1}" "secret1" "${_groupA_user1}" "secret" _cpw "${_groupB_user1}" "secret" "${_groupB_user1}" "secret1" _cpw "${_groupB_user1}" "secret1" "${_groupB_user1}" "secret" _cpw "${_groupC_user1}" "secret" "${_groupC_user1}" "secret1" _cpw "${_groupC_user1}" "secret1" "${_groupC_user1}" "secret" _testTitle "Testing requirement #2: \"Group A\" members cannot change the passwords of other \"Group A\" members." _cpw "${_groupA_user1}" "secret" "${_groupA_user2}" "secret1" 50 _testTitle "Testing requirement #3: \"Group A\" members can change the passwords of everyone else." _cpw "${_groupA_user1}" "secret" "${_groupB_user1}" "secret1" _cpw "${_groupA_user1}" "secret" "${_groupB_user1}" "secret" _cpw "${_groupA_user1}" "secret" "${_groupC_user1}" "secret1" _cpw "${_groupA_user1}" "secret" "${_groupC_user1}" "secret" _testTitle "Testing requirement #4: \"Group B\" members can change the passwords of \"Group C\" members." _cpw "${_groupB_user1}" "secret" "${_groupC_user1}" "secret1" _cpw "${_groupB_user1}" "secret" "${_groupC_user1}" "secret" _cpw "${_groupB_user1}" "secret" "${_groupB_user2}" "secret1" 50 _testTitle "Testing requirement #5: Deny other access." _cpw "${_groupC_user1}" "secret" "${_groupA_user1}" "secret1" 50 _cpw "${_groupC_user1}" "secret" "${_groupB_user1}" "secret1" 50 _cpw "${_groupC_user1}" "secret" "${_groupC_user2}" "secret1" 50
With the associated output from a successful run:
*** Testing requirement #1: Anyone can change their own password. % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupA_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupA_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret1 cn=groupA_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupA_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupB_user1,ou=people,dc=example,dc=com secret cn=groupB_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupB_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupB_user1,ou=people,dc=example,dc=com secret1 cn=groupB_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupB_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupC_user1,ou=people,dc=example,dc=com secret cn=groupC_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupC_user1,ou=people,dc=example,dc=com secret1 cn=groupC_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 *** Testing requirement #2: "Group A" members cannot change the passwords of other "Group A" members. % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupA_user2,ou=people,dc=example,dc=com secret1 50 modifying entry "cn=groupA_user2,ou=people,dc=example,dc=com" ldap_modify: Insufficient access (50) * Test passed, exit code: 50 *** Testing requirement #3: "Group A" members can change the passwords of everyone else. % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupB_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupB_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupB_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupB_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupC_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupA_user1,ou=people,dc=example,dc=com secret cn=groupC_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 *** Testing requirement #4: "Group B" members can change the passwords of "Group C" members. % _cpw cn=groupB_user1,ou=people,dc=example,dc=com secret cn=groupC_user1,ou=people,dc=example,dc=com secret1 modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupB_user1,ou=people,dc=example,dc=com secret cn=groupC_user1,ou=people,dc=example,dc=com secret modifying entry "cn=groupC_user1,ou=people,dc=example,dc=com" * Test passed, exit code: 0 % _cpw cn=groupB_user1,ou=people,dc=example,dc=com secret cn=groupB_user2,ou=people,dc=example,dc=com secret1 50 modifying entry "cn=groupB_user2,ou=people,dc=example,dc=com" ldap_modify: Insufficient access (50) * Test passed, exit code: 50 *** Testing requirement #5: Deny other access. % _cpw cn=groupC_user1,ou=people,dc=example,dc=com secret cn=groupA_user1,ou=people,dc=example,dc=com secret1 50 modifying entry "cn=groupA_user1,ou=people,dc=example,dc=com" ldap_modify: Insufficient access (50) * Test passed, exit code: 50 % _cpw cn=groupC_user1,ou=people,dc=example,dc=com secret cn=groupB_user1,ou=people,dc=example,dc=com secret1 50 modifying entry "cn=groupB_user1,ou=people,dc=example,dc=com" ldap_modify: Insufficient access (50) * Test passed, exit code: 50 % _cpw cn=groupC_user1,ou=people,dc=example,dc=com secret cn=groupC_user2,ou=people,dc=example,dc=com secret1 50 modifying entry "cn=groupC_user2,ou=people,dc=example,dc=com" ldap_modify: Insufficient access (50) * Test passed, exit code: 50
Additional references:
- "8. Access Control" in the OpenLDAP Administrator's Guide
slapd.access(5)
2 comments:
Hi Mark,
I would like to express my gratitude for not only simply answering my question, but doing it by taking a deep dive in the subject.
(By the way, this time you helped out a sencondary school teacher of informatics from Hungary, and next year a whole school will benefit from the time you've spent on your LDAP articles - although I'll be the only one who cares:))
Thanks again.
raerek
Compound conditionals can be expressed through sets. For example, the following rule means "if the user is the manager of the owner of the object, and he belongs to either Marketing or Sales":
this/owner/manager & ([cn=Marketing]/member* | [cn=Sales]/member*) & user
Post a Comment