Restrict Anonymous Access in 389 Directory Server

Restrict 389 Directory Server anonymous LDAP access with nsslapd-allow-anonymous-access, narrow ldap:///anyone ACIs, disable unauthenticated empty-password binds, and verify root DSE, suffix, and public-attribute behavior.

Published

Updated

Read time 14 min read

Reviewed byDeepak Prasad

389 Directory Server anonymous access policy allowing root DSE discovery while blocking anonymous directory searches

Anonymous LDAP clients can discover your server, enumerate naming contexts, and read directory data without credentials when ACIs permit it. Hardening requires two layers:

  • Server bind policy through nsslapd-allow-anonymous-access
  • Entry and attribute permissions through aci values, especially rules that use userdn="ldap:///anyone"

This guide shows how to audit current exposure, restrict anonymous access to the root DSE or disable it completely, grant only selected public attributes when required, and block unauthenticated empty-password binds that can grant the supplied account's permissions without verifying its password when the feature is enabled.

It builds on ACI examples for syntax and complements advanced ACI bind rules, TLS and StartTLS for transport protection, and effective rights for detailed permission analysis.

Before you start:

IMPORTANT
This guide covers server-level anonymous and unauthenticated bind policy plus ldap:///anyone ACIs for public reads. It does not replace general ACI design, SSF or network bind rules, TLS configuration, anonymous resource limits, or Get Effective Rights analysis. See the dedicated chapters linked above.

Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.

The plain ldap://localhost commands in this guide are for an isolated local lab on instance ldap1 (port 389, suffix dc=example,dc=com). Use StartTLS or LDAPS when connecting across a network.

Changes to nsslapd-allow-anonymous-access do not take effect until you restart the instance. Run dsctl ldap1 restart after every mode change in this guide before you verify behaviour.


Anonymous vs authenticated vs unauthenticated binds

Directory Server evaluates bind policy before ACIs. Test each bind style separately. They are not interchangeable.

Bind type Bind DN Password Server settings involved
Anonymous bind Empty Empty nsslapd-allow-anonymous-access
Unauthenticated bind Non-empty Empty nsslapd-allow-unauthenticated-binds
Authenticated simple bind Non-empty Valid password Normal bind and ACI evaluation

An anonymous bind establishes an anonymous authorization identity. An unauthenticated bind supplies a DN but no password. When nsslapd-allow-unauthenticated-binds is enabled, Red Hat Directory Server can accept an empty-password bind for the supplied account, including Directory Manager, and grant that account's permissions without verifying a password.


How anonymous access is controlled

Layer Control What it decides
Server bind policy nsslapd-allow-anonymous-access Whether empty DN and empty password binds are accepted, and whether root DSE access alone is permitted. Requires an instance restart.
Server bind policy nsslapd-allow-unauthenticated-binds Whether a non-empty DN with an empty password is accepted. Takes effect without a restart.
Entry and attribute permissions aci attributes What the current authorization identity may read, search, compare, or modify

Accepting an anonymous bind does not make every entry public. Applicable ACIs still control each operation. Conversely, setting nsslapd-allow-anonymous-access to off blocks anonymous binds before ACIs are evaluated, even when a ldap:///anyone rule exists on an entry.

Use ldap:///anyone when the permission should apply to anonymous and authenticated clients. Use ldap:///all when only successfully authenticated binds should match.


Audit current anonymous exposure

Read the server-level settings on ldap1:

bash
dsconf ldap1 config get nsslapd-allow-anonymous-access nsslapd-allow-unauthenticated-binds

Sample output:

output
nsslapd-allow-anonymous-access: on
nsslapd-allow-unauthenticated-binds: off

Review ACIs that may grant public access with ldapsearch. Search the entire suffix subtree and inspect every value that references ldap:///anyone:

bash
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "dc=example,dc=com" -s sub "(aci=*)" dn aci

Inspect every returned aci value containing ldap:///anyone. Repeat this search under every user-data suffix. For a server-wide audit, also inspect ACIs under cn=config, because feature and control ACIs are outside the normal data suffixes:

bash
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "cn=config" -s sub "(aci=*)" dn aci

Inspect returned values containing ldap:///anyone. The suffix search and cn=config search serve different purposes; keep both.

Sample output (truncated):

output
dn: dc=example,dc=com
aci: (targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)

dn: ou=People,dc=example,dc=com
aci: (targetattr="uid || cn || sn || mail || telephoneNumber || description || objectClass || gidNumber || uidNumber || homeDirectory || loginShell")...

The domain rule lets anonymous clients read selected attributes on objectClass=domain entries. Delegated-administration and tenant rules on descendant entries appear in the same result set.

To examine ACIs on the suffix entry alone, repeat the search with base scope:

bash
ldapsearch -LLL -o ldif-wrap=no -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "dc=example,dc=com" -s base "(objectClass=*)" aci

Query the root DSE without credentials. Clients use this step to discover namingContexts and LDAP version support:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts supportedLDAPVersion

Sample output:

output
dn:
namingContexts: dc=example,dc=com
supportedLDAPVersion: 2
supportedLDAPVersion: 3

Search the directory suffix without credentials:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dn

Sample output:

output
dn: dc=example,dc=com

dn: dc=tenant1,ou=Tenants,dc=example,dc=com

dn: dc=tenant2,ou=Tenants,dc=example,dc=com

Anonymous clients receive DNs for entries the current ACIs expose. In this lab that means domain entries through the ldap:///anyone rule, not necessarily every user entry.

Search for user entries under ou=people without credentials:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=people,dc=example,dc=com" "(objectClass=*)" dn cn mail

The command exits successfully but returns no entries when no ACI grants anonymous access to people entries.

Filter on a sensitive attribute anonymously to see whether search rights exist without read rights. First confirm that matching data exists as Directory Manager:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -b "ou=people,dc=example,dc=com" "(mail=*)" dn mail

Sample output:

output
dn: uid=user1,ou=people,dc=example,dc=com
mail: [email protected]

dn: uid=user2,ou=people,dc=example,dc=com
mail: [email protected]

Then run the same filter anonymously:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=people,dc=example,dc=com" "(mail=*)" dn

When the administrative search confirms that matching entries exist, an empty anonymous result indicates that the current anonymous permissions do not allow those entries to be matched and returned. Review both search permission on mail and any other ACIs affecting entry visibility.


Configure the anonymous-access mode

Value Behaviour
on Accept anonymous binds; ACIs determine what anonymous clients can read
rootdse Permit anonymous root DSE access only; reject anonymous searches against directory entries
off Reject anonymous binds completely, including root DSE queries

rootdse is a practical default when clients need LDAP server discovery through namingContexts but normal directory data should require authentication.

Restrict anonymous access to the root DSE

I'll set rootdse on ldap1 and restart the instance:

bash
dsconf ldap1 config replace nsslapd-allow-anonymous-access=rootdse

The instance must restart before this bind-policy change takes effect:

bash
dsctl ldap1 restart

Sample output:

output
Successfully replaced value(s) for 'nsslapd-allow-anonymous-access': 'rootdse'
Instance "ldap1" has been restarted

The replace and restart succeeded. Anonymous clients can read the root DSE again, but suffix searches should now fail.

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts supportedLDAPVersion

Sample output:

output
dn:
namingContexts: dc=example,dc=com
supportedLDAPVersion: 2
supportedLDAPVersion: 3

Confirm anonymous suffix searches are rejected:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dn

Sample output:

output
Inappropriate authentication (48)
Additional information: Anonymous access is not allowed.

Result code 48 confirms anonymous suffix searches are blocked while root DSE discovery still works under rootdse.

Confirm authenticated users still search according to their ACIs. This test assumes user1, /root/user1.pw, and an applicable read ACI were prepared in the self-service ACI chapter. Otherwise, use any authenticated account and password file that can read a known entry.

bash
ldapsearch -LLL -x -H ldap://localhost:389 -D "uid=user1,ou=people,dc=example,dc=com" -y /root/user1.pw -b "uid=user1,ou=people,dc=example,dc=com" -s base "(objectClass=*)" cn mail

The result depends on self-service or other ACIs configured for user1. A successful authenticated search confirms the global anonymous restriction did not remove normal user access.

Disable anonymous binds completely

When no client requires anonymous discovery, set the policy to off and restart:

bash
dsconf ldap1 config replace nsslapd-allow-anonymous-access=off

Restart ldap1 so anonymous binds are rejected immediately:

bash
dsctl ldap1 restart

Verify with an anonymous suffix search:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com"

Sample output:

output
ldap_bind: Inappropriate authentication (48)
	additional info: Anonymous access is not allowed

Red Hat Directory Server documents this result when anonymous access is disabled. Root DSE queries fail the same way under off:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts

Sample output:

output
ldap_bind: Inappropriate authentication (48)
	additional info: Anonymous access is not allowed

Configure narrow public access

When a phone book or public staff directory is required, enable anonymous binds only for the ACI example, then create a dedicated subtree and grant read access to selected attributes.

Enable anonymous access for the public-directory example:

bash
dsconf ldap1 config replace nsslapd-allow-anonymous-access=on

Restart the instance so the server accepts anonymous binds for the ACI tests below:

bash
dsctl ldap1 restart

Suitable public fields include:

text
cn
sn
givenName
telephoneNumber

Save the following as /tmp/public-ou.ldif:

ldif
dn: ou=Public,dc=example,dc=com
objectClass: organizationalUnit
ou: Public

dn: cn=Jane Public,ou=Public,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Jane Public
sn: Public
givenName: Jane
telephoneNumber: +1-555-0100
mail: [email protected]

Create the container and sample entry as Directory Manager:

bash
ldapadd -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/public-ou.ldif

Sample output:

output
adding new entry "ou=Public,dc=example,dc=com"

adding new entry "cn=Jane Public,ou=Public,dc=example,dc=com"

Both entries were accepted. The public-directory ACI in the next step applies only to ou=Public.

ldif
dn: ou=Public,dc=example,dc=com
changetype: modify
add: aci
aci: (targetattr="cn || sn || givenName || telephoneNumber")(version 3.0; acl "Allow public directory lookup"; allow (read,search,compare) userdn="ldap:///anyone";)

Apply the ACI:

bash
ldapmodify -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/public-aci.ldif

Include objectClass in targetattr with search permission when clients use (objectClass=*) filters. This example uses an attribute the ACI already permits:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "ou=Public,dc=example,dc=com" "(cn=Jane Public)" cn sn givenName telephoneNumber mail

Sample output:

output
dn: cn=Jane Public,ou=Public,dc=example,dc=com
cn: Jane Public
sn: Public
givenName: Jane
telephoneNumber: +1-555-0100

mail is omitted because it is outside the allow list. Anonymous clients receive only the public attributes you named.

Protect sensitive attributes from anonymous searches

Do not expose attributes such as:

text
userPassword
mail
mobile
homePhone
postalAddress
employeeNumber
memberOf
nsRole
aci

Prefer an explicit targetattr allow list. Avoid targetattr="*" with a short deny list. That pattern is difficult to audit and easy to over-grant.

Test both direct reads and filter searches. Granting read without search, or the reverse, produces different client behaviour. Verify that sensitive attributes cannot be used in anonymous filters when policy requires it.

Remove a public-access ACI safely

Follow the safe update workflow from ACI examples:

  1. List every aci value on the affected entry with ldapsearch.
  2. Identify the exact public-access rule. For example, look for Enable anyone domain read on dc=example,dc=com.
  3. Delete only that value with a targeted delete: aci modify operation.
  4. Retest anonymous and authenticated searches.

The following LDIF removes only the Allow public directory lookup ACI created in this lab. To remove another public rule, retrieve and copy that rule's exact stored value into the delete: aci operation.

Save the following as /tmp/remove-public-aci.ldif:

ldif
dn: ou=Public,dc=example,dc=com
changetype: modify
delete: aci
aci: (targetattr="cn || sn || givenName || telephoneNumber")(version 3.0; acl "Allow public directory lookup"; allow (read,search,compare) userdn="ldap:///anyone";)

Apply the targeted ACI delete as Directory Manager:

bash
ldapmodify -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw -f /tmp/remove-public-aci.ldif

Do not delete the entire aci attribute unless you intend to remove every rule on the entry.


Disable unauthenticated empty-password binds

An unauthenticated bind supplies a non-empty bind DN with an empty password. This is different from an anonymous bind, which supplies neither a DN nor a password.

Keep unauthenticated binds disabled:

bash
dsconf ldap1 config replace nsslapd-allow-unauthenticated-binds=off

This setting takes effect without restarting the instance. Red Hat Directory Server documents off as the default.

Verify that a DN with an empty password is rejected:

bash
ldapwhoami -x -H ldap://localhost:389 -D "uid=user1,ou=people,dc=example,dc=com" -w ''

Sample output:

output
ldap_bind: Server is unwilling to perform (53)
	additional info: Unauthenticated binds are not allowed

Do not enable nsslapd-allow-unauthenticated-binds on a shared or production server. When enabled, Directory Server can accept an empty password for a supplied account DN and grant that account's permissions, including a privileged account, without verifying the password.


Production considerations

Before you change production policy, test clients that may rely on anonymous LDAP:

  • SSSD and operating-system identity stacks
  • LDAP address-book applications
  • Monitoring and load-balancer health checks
  • Legacy applications that read namingContexts before binding

Prefer rootdse over off when a client needs anonymous server discovery but must not read directory entries. A second instance on the same host, such as ldap2 on port 1389 from multiple instances, can hold a separate policy for isolated client testing without changing production ldap1 settings.

Even with narrow ACIs, anonymous clients can consume server resources. Directory Server can limit anonymous search result size, execution time, idle time, and the number of candidate entries examined by configuring a limit template and referencing it through nsslapd-anonlimitsdn. Configure those controls in the dedicated resource limits chapter when anonymous access must remain enabled under strict quotas.


Verify and finalize the policy

After hardening, confirm behaviour matches intent:

Test Expected result
Anonymous root DSE search Allowed with on or rootdse; denied with off
Anonymous suffix search Denied with rootdse or off; depends on ACIs with on
Anonymous public-attribute search Allowed only under entries with a narrow ldap:///anyone ACI
Anonymous sensitive-attribute search Denied unless explicitly and intentionally granted
Valid authenticated user search Controlled by normal ACIs—not by the anonymous bind settings
Bind DN with empty password Denied when nsslapd-allow-unauthenticated-binds is off
Valid bind DN and password Allowed when credentials and ACIs permit the operation
Symptom Likely cause Fix
Anonymous searches still return entries Inherited ACI contains userdn="ldap:///anyone" Search aci on ancestors with subtree scope; delete or narrow the public rule
Root DSE search fails after hardening Mode is off instead of rootdse Set nsslapd-allow-anonymous-access=rootdse and restart when discovery is required
Mode change has no effect Instance not restarted after changing nsslapd-allow-anonymous-access Run dsctl ldap1 restart before retesting
Application stops working after hardening Client performs anonymous discovery or pre-bind searches Test with rootdse; update the client to bind before searching
Authenticated users also lose access An ACI was deleted or modified during cleanup Restore the intended ACI; anonymous bind settings do not replace user ACIs
Empty-password bind succeeds with account permissions nsslapd-allow-unauthenticated-binds is on Set the value to off; treat this setting as substantially more dangerous than anonymous bind
Anonymous user can search by a hidden attribute search granted without read, or vice versa, through another ACI Review inherited rules; remove unintended search or compare on sensitive attributes
Users can display the complete membership list An ACI grants read on member Remove or narrow the read grant if membership must remain private
Users can test or infer whether a DN is a member An ACI grants search or compare on member Remove or narrow those rights when membership queries must also be restricted

Result code 48 in this workflow normally means the active anonymous-access mode rejected the bind. Confirm the configured value with dsconf ldap1 config get nsslapd-allow-anonymous-access and verify that the instance was restarted. For result code 50 after a successful bind, see fix LDAP error 50.

Set the final anonymous-access policy and clean up

Remove lab objects when you no longer need them:

bash
ldapdelete -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw "cn=Jane Public,ou=Public,dc=example,dc=com"

Delete the empty public container after the sample entry is gone:

bash
ldapdelete -x -H ldap://localhost:389 -D "cn=Directory Manager" -y /root/dm.pw "ou=Public,dc=example,dc=com"

Use on only when you deliberately retain public ldap:///anyone access and have reviewed every applicable ACI:

bash
# Optional: use only when intentionally retaining public anonymous access
dsconf ldap1 config replace nsslapd-allow-anonymous-access=on

Restart after that optional change if you enable full anonymous access again:

bash
dsctl ldap1 restart

Leave the lab in a hardened state. Set rootdse as the default final policy. That is the last mode change in this guide.

bash
dsconf ldap1 config replace nsslapd-allow-anonymous-access=rootdse

Restart once more so the final rootdse policy is active:

bash
dsctl ldap1 restart

Verify root DSE discovery succeeds and suffix searches fail:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)" namingContexts

Sample output:

output
dn:
namingContexts: dc=example,dc=com

Confirm that anonymous clients can no longer search the data suffix:

bash
ldapsearch -LLL -x -H ldap://localhost:389 -b "dc=example,dc=com" "(objectClass=*)" dn

Sample output:

output
Inappropriate authentication (48)
Additional information: Anonymous access is not allowed.

Remove /root/user1.pw only when you created it specifically for this lab and no later chapter requires it.

bash
# Optional lab cleanup
rm -f /root/user1.pw

What's Next


References


Summary

  1. Audit anonymous searches and every ldap:///anyone ACI before you change bind policy.
  2. Restart ldap1 after every nsslapd-allow-anonymous-access change before you verify results.
  3. Use rootdse when clients need anonymous server discovery without reading directory entries.
  4. Use off when anonymous binds must be rejected completely.
  5. Grant public access only through explicit attribute allow lists on dedicated entries.
  6. Keep nsslapd-allow-unauthenticated-binds disabled. Enabling it can grant a supplied account's permissions without a password.
  7. Test applications before and after changing policy; distinguish ldap:///anyone from ldap:///all.

Frequently Asked Questions

1. What is the difference between anonymous and unauthenticated LDAP binds?

An anonymous bind uses an empty DN and empty password. An unauthenticated bind supplies a non-empty DN with an empty password. Keep nsslapd-allow-unauthenticated-binds disabled because enabling it can allow an empty-password bind to receive the permissions of the supplied account, including a privileged account.

2. What does nsslapd-allow-anonymous-access rootdse do?

The rootdse value permits anonymous clients to read the root DSE for server discovery while rejecting anonymous searches against normal directory entries. Use it when clients need namingContexts or supportedLDAPVersion without exposing user data. Restart the instance after changing this setting.

3. What is the difference between ldap:///anyone and ldap:///all in an ACI?

ldap:///anyone matches anonymous and authenticated clients. ldap:///all matches only successfully authenticated binds. Public directory ACIs use anyone; authenticated-only rules use all.

4. Why can anonymous clients filter on an attribute they cannot read?

read, search, and compare are separate permissions in Directory Server. An ACI can grant search on member or mail without granting read, so a client may match entries in a filter without receiving the stored attribute values.

5. Does disabling anonymous access break LDAP client discovery?

Some clients query the root DSE anonymously to learn namingContexts before they bind. Prefer rootdse over off when discovery is required but directory data must stay protected. Test SSSD, monitoring probes, and address-book tools before you set off.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …