Secure

Let’s take a few steps to block casual access, stop using the admin account, and validate the certificates we’re using.

LDAP

Require Passwords

By default, the LDAP server allows anonymous connections to read all the user entry data. Let’s prevent that by disabling anonymous binds.

Before we make any changes, this anonymous bind and search will return all the entries.

ldapsearch -x -LLL -b dc=example,dc=org

Let’s create a LDIF config file to disable that, load it and test again

vi disable_anon_bind.ldif
dn: cn=config
changetype: modify
add: olcDisallows
olcDisallows: bind_anon
# Modify the config
ldapmodify -H ldapi:/// -Y EXTERNAL -f disable_anon_bind.ldif

# An anonymous bind will now fail
ldapsearch -x -LLL -b dc=example,dc=org

# An authenticated search will succeed
ldapsearch -x -LLL -W -D cn=admin,dc=example,dc=org -b dc=example,dc=org

Require Encryption

OpenLDAP accepts TLS connections, but it doesn’t require it. Now that we’re requiring passwords we should also require encryption. NOTE make sure systems talking to your LDAP server can use TLS and ignore/trust the Let’s Encrypt trust chain.

vi require-tls.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcSecurity
olcSecurity: tls=1
ldapmodify -H ldapi:// -Y EXTERNAL -f require-tls.ldif

See if it requires encryption.

# Test without -ZZ for no TLS
ldapsearch -x -LLL -W  -D cn=admin,dc=example,dc=org -b dc=example,dc=org -h wifi.example.org
Enter LDAP Password:
ldap_bind: Confidentiality required (13)
        additional info: TLS confidentiality required

Create Service Accounts

Services should have their own accounts, so let’s create one for FreeRADIUS and a theoretical Identity Management Service. These accounts bind directly so we’ll set their passwords traditionally.

vi service_accts.ldif

dn: cn=FreeRADIUS,dc=example,dc=org
objectClass: person
cn: FreeRADIUS
sn: Server
description: service

dn: cn=IDM,dc=example,dc=org
objectClass: person
cn: IDM
sn: Server
description: service

ldapadd -x -W -D cn=admin,dc=example,dc=org -f service_accts.ldif

ldappasswd -x -W -D cn=admin,dc=example,dc=org -S cn=FreeRADIUS,dc=example,dc=org
ldappasswd -x -W -D cn=admin,dc=example,dc=org -S cn=IDM,dc=example,dc=org 

Add ACLs

The password attribute is protected by default. The FreeRADIUS account must read and the IDM account write it, so lets replace the ACL on that attribute.

vi FreeRADIUS-IDM-password.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword by self write by anonymous auth by dn="cn=FreeRADIUS,dc=example,dc=org" read by dn="cn=IDM,dc=example,dc=org" write by * none
olcAccess: {1}to attrs=shadowLastChange by self write by * read
olcAccess: {2}to * by * read

The IDM account also gets the ability to manage the whole folder, so as to add and delete people.

vi IDM-Access.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {2}to dn.exact="ou=people,dc=example,dc=org" attrs=children by dn="cn=IDM,dc=example,dc=org" manage
olcAccess: {3}to dn.children="ou=people,dc=example,dc=org" by dn="cn=IDM,dc=example,dc=org" manage
ldapmodify -Y EXTERNAL -H ldapi:/// -f FreeRADIUS-IDM-password.ldif
ldapmodify -Y EXTERNAL -H ldapi:/// -f idm-access.ldif

The FreeRADIUS user should now be able to query a user and see their password value

ldapsearch -x -LLL -W -D cn=FreeRADIUS,dc=example,dc=org -b dc=example,dc=org

...
userPassword:: .....

If you mess something up and need to reset ACLs, or understand more about the rules, see the miscellany.

RADIUS

Use New Account

Now that you’ve created an account for FreeRADIUS, you should go back and update it’s connection and test that it still works.

vi /etc/freeradius/3.0/mods-available/ldap

        identity = 'cn=FreeRADIUS,dc=example,dc=org'
        password = somePasswordYouSet

Validate Certs

The server processes themselves are not validating the certs and that’s bad form. We’ve been skipping validation because in the past it was a hurdle. But Debian 12 and later include the ISRG Root X1 that Let’s Encrypt uses. Let’s test that now.

# And test with the CA option. 
# You need to make sure the host name matches 
echo 127.0.1.1  wifi.example.org >> /etc/hosts

ldapsearch -x -LLL -W -ZZ -D cn=admin,dc=example,dc=org -b dc=example,dc=org -h wifi.example.org

If this works as expected, you can attempt setting validate in the RADIUS settings.

vi /etc/freeradius/3.0/mods-available/ldap

                start_tls       = yes
                ca_file         = /etc/ssl/certs/ca-certificates.crt
                require_cert    = 'demand'

If these don’t work, you can compile a trust chain as described in the miscellany.

Troubleshooting

LDAP Error Messages

main: TLS init def ctx failed: -1

slapd can’t read the cert. Check the groups and unix permissions.

ldap_start_tls: Connect error (-11)

Check that the file you are specifying in ldapsearch, such LDAPTLS_CACERT=/root/r3-x1.pem, is actually available. Make sure you are using the -h and that the host you are connecting to matches the CN in the certificate. If it’s different, you’ll need to use a hosts file entry to trick the client

Nothing useful in logs

You can increase the log level from ‘stats’ to ‘args` with an ldapmodify. Just make sure to put it back when you’re done.

vi loglevel.ldif
dn: cn=config
changetype:modify
replace: olcLoglevel
#olcLoglevel: stats
olcLoglevel: args
ldapmodify -Y EXTERNAL -H ldapi:/// -b cn=config -D cn=config -s base -LLL -W -f loglevel.ldif

Next Steps

You’ve done a lot of work to get to this point where you can finally apply some Role Based Access Controls with Dynamic VLAN assignment.

Dynamic VLAN Assignment


Last modified July 31, 2025: nac additions (3334349)