Виникло завдання зробити зовнішлю авторизацію через LDAP для сервіса, який її не підтримував, зате підтримував авторизацію через Radius. А у radius’a виявився модуль LDAP.
Тестовий стенд: Debian Linux 11.6 + Freeradius.
Нам знадобляться пакети freeradius-ldap, freeradius-utils (для локального тесту через утиліту radtest).
Файл clients.conf (описуються клієнти, які будуть підключатися, на кожного клієнта окрема секція)
client srv_auth {
ipaddr = X.X.X.X
secret = supersecretpass
}
Якщо ви невірно напишите secret на клієнті, то в логах (якщо ввімкнути debug паролів) паролі будуть ієрогліфами. Інакше – у відкритому виглядя. Це зовсім не означає, що паролі передаються у відкритому вигляді, вони шифруються цим secret’ом на клієнті і розшифровуються на сервері. Якщо secret не співпадає, то розшифрований пароль виглядає як ієрогліф.
Файл dictionary (типи авторизації, які будемо використовувати)
VALUE Auth-Type LDAP 5
Робимо symlink: mods-available/ldap -> mod-enable/ldap і міняємо таке (бо там багато чого по замовчуванню):
ldap {
server = 'ldaps://ldap.domain.com:636'
port = 636
identity = 'cn=radius,ou=services,dc=domain,dc=com'
password = 'megapass'
base_dn = 'dc=domain,dc=com'
sasl {
}
...
user_dn = "LDAP-UserDn"
user {
base_dn = "${..base_dn}"
filter = "(&(objectClass=gosaMailAccount)(uid=%{%{Stripped-User-Name}:-%{User-Name}})(!(sn=blocked_*)))"
sasl {
}
scope = 'sub'
}
group {
base_dn = "${..base_dn}"
filter = '(objectclass=organizationalRole)'
scope = 'sub'
name_attribute = cn
membership_filter = "(roleOccupant=uid=%{%{Stripped-User-Name}:-%{User-Name}},ou=people,dc=domain,dc=com)"
membership_attribute = 'memberOf'
}
...
tls {
require_cert = 'never'
}
...
Трохи поясню. Заблоковані користувачі у LDAP у мене додатково мають фільтр ‘sn=blocked_’, тому їх треба теж враховувати. Додатково, треба видавати доступи на основі груп, тобто, якщо користувач є у групі developer або vpn – дати доступ. Нагадаю, що належність користувача до груп у LDAP’і має 2 реалізації: користувачі описані в самій групі, або користувач має опис відвовідних груп. У мене перший варіант. Якщо у вас другий – то треба змінити membership_attribute. Умови належності до певних груп будуть далі. Тут лише те, що стосується користувача.
Файл users
DEFAULT Auth-Type := LDAP
Fall-Through = 1
DEFAULT LDAP-Group == "developer"
DEFAULT Auth-Type := Reject
Reply-Message = "Sorry, you're not part of an authorized group."
Файл sites-enabled/default
authorize {
...
ldap
if ((ok || updated) && User-Password) {
update {
control:Auth-Type := ldap
}
}
...
}
...
authenticate {
...
Auth-Type LDAP {
ldap
}
...
}
...
post-auth {
if (LDAP-Group == "developer") {
noop
}
elsif (LDAP-Group == "vpn") {
noop
}
else {
reject
}
...
Ось тут влане і описуються всі 2 групи, які успішно авторизуються.
На цьому з конфігами все. Для тестування авторизації можна застосувати radtest з пакету freeradius-utils:
$ radtest -x testsuser MegaSuperveryPassword 127.0.0.1 2 testing123
Для debug режиму рекомендую вимкнути сервіс і запустити вручну:
# freeradius -X
Ось, як виглядає, коли користувач не в групі:
rlm_ldap (ldap): Released connection (1)
(0) [ldap] = ok
(0) } # Auth-Type LDAP = ok
(0) # Executing section post-auth from file /etc/freeradius/3.0/sites-enabled/default
(0) post-auth {
(0) if (LDAP-Group == "developer") {
(0) Searching for user in group "developer"
rlm_ldap (ldap): Reserved connection (0)
(0) Using user DN from request "uid=testsuser,ou=people,dc=domain,dc=com"
(0) Checking for user in group objects
(0) EXPAND (&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=%{%{Stripped-User-Name}:-%{User-Name}},ou=people,dc=domain,dc=com))
(0) --> (&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))
(0) Performing search in "dc=domain,dc=com" with filter "(&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))", scope "sub"
(0) Waiting for search result...
(0) Search returned no results
(0) Checking user object's memberOf attributes
(0) Performing unfiltered search in "uid=testsuser,ou=people,dc=domain,dc=com", scope "base"
(0) Waiting for search result...
(0) No group membership attribute(s) found in user object
rlm_ldap (ldap): Released connection (0)
(0) User is not a member of "developer"
(0) if (LDAP-Group == "developer") -> FALSE
(0) else {
(0) [reject] = reject
(0) } # else = reject
(0) } # post-auth = reject
А ось так, коли є:
rlm_ldap (ldap): Released connection (0)
(0) [ldap] = ok
(0) } # Auth-Type LDAP = ok
(0) # Executing section post-auth from file /etc/freeradius/3.0/sites-enabled/default
(0) post-auth {
(0) if (LDAP-Group == "developer") {
(0) Searching for user in group "developer"
rlm_ldap (ldap): Reserved connection (1)
(0) Using user DN from request "uid=testsuser,ou=people,dc=domain,dc=com"
(0) Checking for user in group objects
(0) EXPAND (&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=%{%{Stripped-User-Name}:-%{User-Name}},ou=people,dc=domain,dc=com))
(0) --> (&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))
(0) Performing search in "dc=domain,dc=com" with filter "(&(cn=developer)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))", scope "sub"
(0) Waiting for search result...
(0) Search returned no results
(0) Checking user object's memberOf attributes
(0) Performing unfiltered search in "uid=testsuser,ou=people,dc=domain,dc=com", scope "base"
(0) Waiting for search result...
(0) No group membership attribute(s) found in user object
rlm_ldap (ldap): Released connection (1)
(0) User is not a member of "developer"
(0) if (LDAP-Group == "developer") -> FALSE
(0) elsif (LDAP-Group == "vpn") {
(0) Searching for user in group "vpn"
rlm_ldap (ldap): Reserved connection (0)
(0) Using user DN from request "uid=testsuser,ou=people,dc=domain,dc=com"
(0) Checking for user in group objects
(0) EXPAND (&(cn=vpn)(objectclass=organizationalRole)(roleOccupant=uid=%{%{Stripped-User-Name}:-%{User-Name}},ou=people,dc=domain,dc=com))
(0) --> (&(cn=vpn)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))
(0) Waiting for bind result...
(0) Bind successful
(0) Performing search in "dc=domain,dc=com" with filter "(&(cn=vpn)(objectclass=organizationalRole)(roleOccupant=uid=testsuser,ou=people,dc=domain,dc=com))", scope "sub"
(0) Waiting for search result...
(0) User found in group object "dc=domain,dc=com"
rlm_ldap (ldap): Released connection (0)
(0) elsif (LDAP-Group == "vpn") -> TRUE
(0) elsif (LDAP-Group == "vpn") {
(0) [noop] = noop
(0) } # elsif (LDAP-Group == "vpn") = noop