For the complete documentation index, see llms.txt. This page is also available as Markdown.

LDAP / AD Authentication

中文版本:LDAP / AD 认证后端

ToughRADIUS can verify a user's password against an external LDAP directory or Microsoft Active Directory by performing an LDAP bind, instead of (or in addition to) the password stored in its own database. This lets you reuse an existing corporate directory without copying or migrating passwords.

PAP-family only. The LDAP backend authenticates by binding with the cleartext password, so it serves only bare PAP and EAP-TTLS inner PAP (RFC 5281). Challenge/response methods — CHAP, MS-CHAP, MS-CHAPv2, EAP-MD5 and PEAP-MSCHAPv2 — cannot be served, because the server never holds the cleartext password it would need to recompute their responses. When LDAP is enabled those methods are deliberately rejected with a diagnostic reason; see How authentication flows below.

When to use it (and when not to)

Use the LDAP backend when you must serve users whose passwords already live in a directory — an OpenLDAP server, FreeIPA, or Active Directory — and you do not want to provision a certificate per client. It is the pragmatic bridge for mixed and legacy estates: protect the tunnel with a server certificate, then send the username and password inside it.

Be honest about the trade-off. EAP-TTLS/PAP transmits the cleartext password inside the TLS tunnel — it is protected only by that tunnel. The security of the deployment therefore rests on a strong, properly verified server certificate and TLS 1.2+. If every client can present a certificate, prefer EAP-TLS. If you need directory-backed passwords for legacy clients, LDAP + EAP-TTLS/PAP is the right tool — just size your expectations accordingly.

Deployment model

The LDAP backend replaces only the password check. It does not create accounts or carry authorization data. For every user you still need a local RadiusUser row, because authorization (profile/plan, rate limits, expiry, concurrent-session limit, address pool, VLAN, MAC binding) is loaded from the local database before authentication runs.

In other words:

  • Authentication (is the password correct?) → the LDAP directory, by bind.

  • Authorization (what is this user allowed to do?) → the local RadiusUser row, exactly as without LDAP.

A global ldap.Enabled switch turns the backend on or off for the whole server; there is no per-user "authentication source" field. MAC-address authentication always bypasses LDAP.

Configuration

Configure the backend on the System Configuration page of the admin UI, under the LDAP group. All items are also editable through the settings API. The backend re-reads its configuration on every authentication attempt, so changes take effect immediately without a restart.

Key
Type
Default
Applies to
Description

ldap.Enabled

bool

false

both

Turn the LDAP backend on. Off by default.

ldap.ServerURL

string

(empty)

both

Directory URL, e.g. ldap://dc.example.com:389 or ldaps://dc.example.com:636.

ldap.BindMode

enum

template

both

template or search (see below).

ldap.BindDNTemplate

string

(empty)

template

DN template with a single %s for the username, e.g. uid=%s,ou=people,dc=example,dc=com or the AD UPN form %s@example.com.

ldap.BaseDN

string

(empty)

search

Subtree base for the user lookup, e.g. dc=example,dc=com.

ldap.UserFilter

string

(uid=%s)

search

Filter with a single %s for the username (escaped before substitution), e.g. (uid=%s) or AD (sAMAccountName=%s).

ldap.SearchBindDN

string

(empty)

search

DN of the read-only service account used to find users, e.g. cn=svc-radius,ou=svc,dc=example,dc=com.

ldap.SearchBindPassword

string

(empty)

search

Password for the service-account DN.

ldap.StartTLS

bool

false

both

Upgrade an ldap:// connection to TLS with StartTLS (RFC 4513 §3) before binding. Leave off for ldaps://.

ldap.TLSSkipVerify

bool

false

both

Skip TLS certificate verification. Insecure — lab / self-signed only.

ldap.Timeout

int (s)

5

both

Dial and per-operation timeout in seconds (1–60).

Bind modes

Template mode (ldap.BindMode = template) is the simplest: the username is substituted into BindDNTemplate to form a DN, and the server binds directly as that DN with the supplied password. Use it when every user's DN follows a fixed pattern.

Search mode (ldap.BindMode = search) handles directories where the DN is not predictable. The server first binds as a read-only service account (SearchBindDN / SearchBindPassword), searches under BaseDN with UserFilter to locate the user's DN, and then re-binds as the user with the supplied password to verify it.

Transport security

  • Use an ldaps:// URL for implicit TLS (port 636), or an ldap:// URL together with StartTLS = true to upgrade the plaintext connection (RFC 4513 §3). Do not enable StartTLS on an ldaps:// URL.

  • Leave TLSSkipVerify = false in production. Enable it only against a lab or self-signed directory — it disables certificate verification and exposes the bind to interception.

How authentication flows

Method

Behaviour when ldap.Enabled = true

Bare PAP

Authenticated by LDAP bind.

EAP-TTLS / inner PAP

Authenticated by LDAP bind; MS-MPPE keys are still derived for the tunnel.

CHAP / MS-CHAP / MS-CHAPv2

Rejected with a diagnostic reason (no cleartext password to bind with).

EAP-MD5 / PEAP-MSCHAPv2

Rejected for the same reason.

MAC authentication

Bypasses LDAP entirely.

The rejection of challenge/response methods is deliberate and centralized at the password-retrieval boundary, not forked at the protocol ingress. When LDAP is active the local RadiusUser.Password is usually empty, and a challenge/response method that derived its expected value from an empty secret could otherwise falsely accept any user — so those methods are failed closed instead.

Security model

The backend is conservative by design:

  • Empty username or password is rejected before any network operation. A bind with a DN and an empty password is treated as an anonymous bind by many servers and would otherwise succeed (RFC 4513 §5.1.2), so it is refused up front.

  • Injection is neutralized. In search mode the username is escaped with LDAP filter escaping (RFC 4515 §3); in template mode it is DN-escaped before substitution.

  • An ambiguous search is refused. If UserFilter matches more than one entry, the attempt is rejected rather than guessing.

  • A directory outage is never reported as a wrong password. An invalid- credentials result (LDAP code 49) maps to a password rejection; every other failure — unreachable directory, misconfiguration, service-account bind failure — maps to a backend-unavailable outcome. This distinction also drives the metrics below.

Observability

Rejections are counted by reason (see the Operations Guide for the metrics endpoint):

  • radus_reject_passwd_error — the password was wrong (bind returned invalid-credentials).

  • radus_reject_ldap_error — the backend could not give an answer (directory unreachable, StartTLS failure, service-account bind failure, misconfiguration).

Keeping these separate means a directory outage shows up as radus_reject_ldap_error, not as a spike of "wrong password" — so an alert on radus_reject_ldap_error cleanly signals a directory problem rather than user error.

Note. radus_accept increments once per successful authentication — both the bare PAP/CHAP and the EAP flows converge on a single accept chokepoint — so it pairs with the reject counters above for a success-rate or SLO view. A directory outage still surfaces only as radus_reject_ldap_error, never as a false radus_accept, keeping the success and failure signals separate.

Troubleshooting

Symptom
Likely cause

Every login fails, radus_reject_ldap_error climbing

ServerURL wrong/unreachable, TLS handshake failing, or (search mode) the service account cannot bind.

Windows/AD clients rejected, PAP users fine

The clients are using PEAP-MSCHAPv2 or MS-CHAPv2, which LDAP cannot serve — switch them to EAP-TTLS/PAP or use the local password backend.

Search mode finds no user

Check BaseDN and UserFilter; confirm the service account can read user entries.

Bind works in the lab but fails in production

TLSSkipVerify was masking an invalid certificate — install a trusted certificate and turn it off.

Passwords accepted but the session has no plan/limits

The local RadiusUser row is missing — LDAP only checks the password; create the local account for authorization.

See also

Last updated