I had a simple ACL in place on my server that was working fine. I've decided to set up SSSD to authenticate user logins via LDAP, so I need to give more access to the SSSD bind account. In the process I've somehow blocked all access beyond the first ACL; despite the break
statement at the end of ACL {0}, every search that isn't by a root-level user returns error 32 (object not found.)
The structure of the database looks roughly like this:
organization: dc=r1,dc=internal
organizationalUnit: ou=users,dc=r1,dc=internal
inetOrgPerson: uid=mike,ou=users,dc=r1,dc=internal
organizationalUnit: ou=groups,dc=r1,dc=internal
groupOfUniqueNames: cn=root,ou=groups,dc=r1,dc=internal
posixGroup: cn=mike,ou=groups,dc=r1,dc=internal
organizationalUnit: ou=system,dc=r1,dc=internal
inetOrgPerson: uid=sssd,ou=system,dc=r1,dc=internal
Here is my ACL:
version: 1
dn: olcDatabase={2}mdb,cn=config
changetype: modify
replace: olcAccess
# admin users can write anything in this subtree
# also the root SASL user (eg ldapmodify -QY EXTERNAL -H ldapi:/// ...)
# nobody else has access, but continue searching for matches below
olcAccess: {0}to dn.subtree="dc=r1,dc=internal"
by anonymous break
by group/groupOfUniqueNames/uniqueMember="cn=root,ou=groups,dc=r1,dc=internal" write
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
by * break
# sssd user can read all user/group attributes
# other users keep looking
olcAccess: {1}to dn.onelevel="ou=users,dc=r1,dc=internal"
by dn.exact="uid=sssd,ou=system,dc=r1,dc=internal" read
by * break
olcAccess: {2}to dn.onelevel="ou=groups,dc=r1,dc=internal"
by dn.exact="uid=sssd,ou=system,dc=r1,dc=internal" read
by * break
# you can update your own password
# anonymous users can authenticate against it
# nobody else sees it
olcAccess: {3}to dn.subtree="dc=r1,dc=internal"
attrs=userPassword
by self write
by anonymous auth
by * none
# anonymous users can read select user/group attributes
olcAccess: {4}to dn.onelevel="ou=users,dc=r1,dc=internal"
attrs=entry,cn,uid,sn,givenName,mail,telephoneNumber,mobile,memberOf
by anonymous read
by * break
olcAccess: {5}to dn.onelevel="ou=groups,dc=r1,dc=internal"
attrs=entry,cn,description,uniqueMember,memberUid
by anonymous read
by * break
# all users can update their own records
# and see all other users' attributes
# everyone (including anonymous) can search
olcAccess: {6}to dn.onelevel="ou=users,dc=r1,dc=internal"
by self write
by users read
by * search
Here is a log extract with extra ACL logging:
Aug 26 13:04:10 lemongrab slapd[3991]: => access_allowed: search access to "ou=users,dc=r1,dc=internal" "entry" requested
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [1] dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => acl_get: [1] matched
Aug 26 13:04:10 lemongrab slapd[3991]: => acl_get: [1] attr entry
Aug 26 13:04:10 lemongrab slapd[3991]: => acl_mask: access to entry "ou=users,dc=r1,dc=internal", attr "entry" requested
Aug 26 13:04:10 lemongrab slapd[3991]: => acl_mask: to all values by "uid=sssd,ou=system,dc=r1,dc=internal", (=0)
Aug 26 13:04:10 lemongrab slapd[3991]: <= check a_dn_pat: anonymous
Aug 26 13:04:10 lemongrab slapd[3991]: <= check a_dn_pat: cn=admin,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: <= check a_group_pat: cn=root,ou=groups,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => mdb_entry_get: found entry: "cn=root,ou=groups,dc=r1,dc=internal"
Aug 26 13:04:10 lemongrab slapd[3991]: <= check a_dn_pat: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
Aug 26 13:04:10 lemongrab slapd[3991]: <= check a_dn_pat: *
Aug 26 13:04:10 lemongrab slapd[3991]: <= acl_mask: [5] applying +0 (break)
Aug 26 13:04:10 lemongrab slapd[3991]: <= acl_mask: [5] mask: =0
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [2] ou=users,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [3] ou=groups,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [4] dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => acl_get: [4] matched
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [5] ou=users,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [6] ou=groups,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: => dn: [7] ou=users,dc=r1,dc=internal
Aug 26 13:04:10 lemongrab slapd[3991]: <= acl_get: done.
Aug 26 13:04:10 lemongrab slapd[3991]: => slap_access_allowed: no more rules
Aug 26 13:04:10 lemongrab slapd[3991]: => access_allowed: no more rules
The log entries confirm my suspicion that nothing much is being done after the first rule. Why, for example, does it jump from rule 1 to rule 4? From my understanding, rule 2 should be considered next.
I've tried both onelevel
and children
scopes in the ACL with the same effect. If I change the ACL to olcAccess: {1}to dn.subtree="dc=r1,dc=internal"
it seems to work, but there are other OUs besides users and groups that I don't want to grant access to. Am I misunderstanding how the scope works?
Update I figured it out! The primary problem is that the documentation is subtle. This is the description of the dnstyle qualifiers in
slapd.access(5)
:The key point is that
dn.one
grants access to only the entries immediately below<dnpattern>
. If you write (as you have):This will fail, because
dn.onelevel="ou=users,dc=r1,dc=internal"
doesn't grant thesssd
user access to the dnou=users,dc=r1,dc=internal
itself. If you look at the ACL logs when making a request, you'll see something like:You have no rules that grant this access, so it fails. We need to add ACLs that grant access to the ou itself. That means replacing something like this:
With this:
(You could replace
read
withsearch
in the first ACL if you don't wantsssd
to read attributes on the ou itself.)Here is the complete set of ACLs I've set up in my test environment:
This works! With a directory structure like this:
Then running:
Produces:
...which is exactly what we want:
sssd
is only able to see a single level, and does not see users underou=nested
. If we perform the same search as a member of theroot
group, we now seeuser3
underou=nested
: