Saltar al contenido principal

OpenLDAP

aviso

Aunque la idea es sincronizar ambos LDAP, en esta versión inicial únicamente desplegaré un LDAP, dejando a futuro la sincronización con el segundo.

Introducción

Los servicios suelen tener opciones para centralizar la autenticación de usuarios, y, aunque últimamente cada vez hay más servicios que utilizan OAuth2, la verdad es que los LDAP siguen siendo la opción con más compatibilidad.

En el mundo de los LDAP podríamos decir que está Microsoft Active Directory (AD) y "los demás". Casi todo está pensado para AD y funciona correctamente con él, si tienes otro, seguramente tendrás problemas por falta de ejemplos o que simplemente no funcione del todo bien.

Aun así, he optado por OpenLDAP debido a que tiene muchos años detrás y porque no quiero seguir manteniendo un Windows Server. Si algún día Microsoft se plantea vender una versión para Linux (como ya hizo con SQL Server), entonces es muy posible que haga el cambio, pero cada vez queda más claro que lo que quieren es que compremos su AD en cloud, así que hacía OpenLDAP.

Instalación

Instalamos el servicio y las utilidades.

sudo apt update
sudo apt install slapd ldap-utils -y

Al terminar nos mostrará un asistente para preguntarnos la contraseña del usuario admin, pero nada más, así que ejecutamos el configurador.

sudo dpkg-reconfigure slapd

En este configuramos el dominio, el nombre de la organización y volvemos a indicar la contraseña del usuario admin.

Omit OpenLDAP server configuration: No
DNS domain name: domain.cat
Organization name: Domain org
Administrator password:
Database backend to use: MDB
Do you want the database to be removed when slapd is purged? No
Move old database? Yes
precaución

El DNS domain name en un LDAP es transformado, así domain.cat se transforma en dc=domain,dc=cat y será la raíz para todo lo demás.

Reiniciamos para aplicar los cambios.

sudo systemctl restart slapd.service

Y verificamos el acceso.

ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
ldapsearch -x -LLL -h 127.0.0.1 -D cn=admin,dc=domain,dc=cat -W -b dc=domain,dc=cat cn
aviso

El usuario administrador de OpenLDAP en distribuciones Debian es 'cn=admin,dc=domain,dc=cat', ajustando el dominio.

TLS

Por defecto, las conexiones a un LDAP no están encriptadas, y debido a que es un gestor de usuarios y contraseñas, esto es mala idea, aun así, hay servicios que no saben (o es realmente complicado) conectar por ldaps que es la versión segura.

Habilitaremos ambas opciones e intentaremos usar el protocolo seguro cuando sea posible.

Antes de empezar necesitamos dos cosas:

  • Definir el DNS utilizado para conectar al LDAP, por ejemplo: dns.domain.intranet.
  • Generar los certificados acorde al DNS elegido, estos son fácilmente creados con esta guía.

Una vez tenemos los pasos previos, copiamos los certificados (key y crt) y el root CA (en versión pem) en la Raspberry.

Creamos la carpeta de destino, asignamos los permisos y movemos los ficheros.

sudo mkdir /etc/ldap/{cacerts,certs}
sudo chown openldap:root ldap.domain.intranet.crt ldap.domain.intranet.key domain.intranet-CA.pem
sudo chmod 400 ldap.domain.intranet.crt ldap.domain.intranet.key
sudo chmod 444 domain.intranet-CA.pem
sudo mv ldap.domain.intranet.* /etc/ldap/certs
sudo mv domain.intranet-CA.pem /etc/ldap/cacerts
important

El certificado root debe tener permisos de lectura a cualquiera ya que lo necesitan las herramientas para poder validar el certificado, sin estos permisos no funcionarán los comandos como ldapsearch u otros como php-ldap

Todas las modificaciones de OpenLDAP se hacen incorporando configuraciones con ficheros ldif, creamos uno para definir el path de estos certificados.

vi ssl.ldif
ssl.ldif
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/cacerts/domain.intranet-CA.pem
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/certs/ldap.domain.intranet.key
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/certs/ldap.domain.intranet.crt

Aplicamos el cambio.

sudo ldapmodify -H ldapi:// -Y EXTERNAL -f ssl.ldif

Además debemos activar el puerto.

sudo vi /etc/default/slapd

Modificamos la línea existente añadiendo el ldaps.

/etc/default/slapd
SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"

Esto es suficiente para que el servidor active la seguridad, pero necesitamos el siguiente añadido para podernos conectar localmente a él.

sudo vi /etc/ldap/ldap.conf
/etc/ldap/ldap.conf
TLS_CACERTDIR /etc/ldap/cacerts
TLS_REQCERT allow

Reiniciamos para aplicar los cambios.

sudo systemctl restart slapd.service

Si todo ha ido bien, veremos que el puerto 636 está abierto y podremos conectar a él

netstat -an | grep 636
ldapsearch -H ldaps://ldap.domain.intranet:636 -x

Interface

Para simplificarnos la vida, necesitaremos algo que nos haga de UI, después de probar varias opciones me decanto por tener dos diferentes:

  • Apache Directory Studio es un Eclipse que se puede instalar en tu PC y conectar a cualquier LDAP, muy fácil de usar y permite prácticamente todo. Para mí, es la opción ideal.
  • Hay veces que no tienes a mano algo capaz de abrir el anterior, así que disponer de una web es un extra, en este caso me he decantado por una versión arreglada de phpLDAPadmin. He probado otras opciones mucho más nuevas, pero todo y funcionar mejor (y ser más bonitas, no nos vamos a engañar), me limitaban demasiado.

Únicamente detallaré cómo configurar el phpLDAPadmin ya que el Apache Directory Studio no tiene misterios, instalar, configurar y ya. Hay que recordar que si conectamos por LDAPS (algo altamente aconsejable), deberemos indicarle la ruta a nuestro certificado root.

phpLDAPadmin

Antes de empezar a instalar, debo señalar algunas cosas:

  • No utilizaré la versión original de sourceforge porque está prácticamente olvidada y da warnings con las versiones de php 7.2 o superiores (en el momento de escribir este artículo, estamos en la 7.3).
  • La versión que utilizaré es la 1.2.5 mantenida por el usuario leenooks.
  • Aun con todo, esta versión tiene un bug, las contraseñas con caracteres especiales fallan, se indica en esta incidencia y se resolverá manualmente en estos pasos. Quizás algún día lo arreglen en el repositorio.

Pi Hole nos instaló lighttpd y php, únicamente nos falta una librería:

sudo apt install php-ldap -y

Lo siguiente es descargar el proyecto, moverlo a la carpeta final y empezar a configurarlo.

sudo git clone https://github.com/leenooks/phpLDAPadmin.git
sudo mv phpLDAPadmin /usr/local/phpldapadmin
sudo cp /usr/local/phpldapadmin/config/config.php.example /usr/local/phpldapadmin/config/config.php
sudo vi /usr/local/phpldapadmin/config/config.php

Lo configuramos para conectar mediante LDAPS, la configuración de host y port es un tanto peculiar, pero así lo indica en la documentación. El usuario de bind se mostrará siempre en el login, así que si personas no-admins pueden acceder a este, quizás sea mejor no definirlo para no darle tantas pistas.

/usr/local/phpldapadmin/config/config.php
$servers->setValue('server','name','Domain org');
$servers->setValue('server','host','ldaps://ldap.domain.intranet:636');
$servers->setValue('server','port',0);
$servers->setValue('server','base',array('dc=domain,dc=cat'));
$servers->setValue('login','bind_id','cn=admin,dc=domain,dc=cat');

En el momento de escribir este artículo, este proyecto tiene un error que no permite utilizar contraseñas complejas, da error al iniciar sesión. Se puede encontrar su estado aquí.

Mientras no sea solucionado, se debe solucionar manualmente.

sudo vi /usr/local/phpldapadmin/htdocs/login.php

Modificamos la línea 22, así debería quedar:

$user['password'] = html_entity_decode(get_request('login_pass'));

Lo siguiente es añadir un alias en el lighttpd.

sudo vi /etc/lighttpd/external.conf
/etc/lighttpd/external.conf
#phpLDAPadmin
alias.url += (
"/phpldapadmin" => "/usr/local/phpldapadmin/htdocs"
)

Reiniciamos el servicio para aplicar todos los cambios (el añadido del php-ldap, la configuración de los certificados para conectar por LDAPS y el nuevo alias).

sudo systemctl restart lighttpd.service

Si todo ha ido bien, ya podremos acceder: https://ldap.domain.intranet/phpldapadmin/

Configuración

Un LDAP puede ser tan simple o complejo como se quiera, al fin y al cabo, es como tener una base de datos con tablas predefinidas, pero donde todo se puede cambiar. Mi consejo es mantener todo lo más sencillo posible y únicamente modificar aquello que realmente necesitemos, los LDAP son complejos (demasiado para mi gusto), y si vamos demasiado allá, quizás nos acabemos perdiendo por el camino.

En mi caso, crearé 3 árboles, uno por cada entorno, debido a que quiero que los usuarios sean diferentes en cada entorno y únicamente quiero tener un LDAP para todos ellos. En muchos casos puede ser preferible tener todos los entornos bajo el mismo árbol, dependerá según las necesidades.

Estructura

Voy a partir de la siguiente estructura, creando tanto las organizaciones principales (entornos), las secundarias (admins, groups, users) y los usuarios, el entorno de Producción no contendrá el usuario de test por razones obvias.

img

Igual que para la configuración del TLS, creamos los ficheros LDIF y los importamos. El primero es para generar los 2 niveles de organizaciones, sin usuarios.

vi tree.ldif
tree.ldif
dn: ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Prod
description: Production environment

dn: ou=admins,ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Admins

dn: ou=groups,ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Groups

dn: ou=users,ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Users

dn: ou=test,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Test
description: Test environment

dn: ou=admins,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Admins

dn: ou=groups,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Groups

dn: ou=users,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Users

dn: ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Dev
description: Dev environment

dn: ou=admins,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Admins

dn: ou=groups,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Groups

dn: ou=users,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: organizationalunit
ou: Users

El siguiente es para los usuarios.

vi users.ldif
users.ldif
dn: uid=admin,ou=admins,ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: admin
cn: Admin Prod
displayname: Admin Prod
sn: Prod
givenname: Admin
mail: admin@domain.cat
userpassword: admin

dn: uid=bind,ou=admins,ou=prod,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: bind
cn: Bind Prod
displayname: Bind Prod
sn: Prod
givenname: Bind
userpassword: bind

dn: uid=admin,ou=admins,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: admin
cn: Admin Test
displayname: Admin Test
sn: Test
givenname: Admin
mail: admin-test@domain.cat
userpassword: admin

dn: uid=bind,ou=admins,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: bind
cn: Bind Test
displayname: Bind Test
sn: Test
givenname: Bind
userpassword: bind

dn: uid=test,ou=users,ou=test,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: test
cn: Test Test
displayname: Test Test
sn: Test
givenname: Test
mail: test-test@domain.cat
userpassword: test

dn: uid=admin,ou=admins,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: admin
cn: Admin Dev
displayname: Admin Dev
sn: Dev
givenname: Admin
mail: admin-dev@domain.cat
userpassword: admin

dn: uid=bind,ou=admins,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: bind
cn: Bind Dev
displayname: Bind Dev
sn: Dev
givenname: Bind
userpassword: bind

dn: uid=test,ou=users,ou=dev,dc=domain,dc=cat
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: test
cn: Test Dev
displayname: Test Dev
sn: Dev
givenname: Dev
mail: test-dev@domain.cat
userpassword: test
aviso

Estos usuarios tienen contraseñas simples debido a que es un ejemplo, todas las contraseñas deben cambiarse por otras seguras.

Únicamente queda importar ambos ficheros.

sudo ldapmodify -a -H ldapi:/// -D cn=admin,dc=domain,dc=cat -W -f tree.ldif
sudo ldapmodify -a -H ldapi:/// -D cn=admin,dc=domain,dc=cat -W -f users.ldif

Permisos

Por defecto, los permisos de OpenLDAP son:

  • El admin puede acceder y modificar cualquier objeto dentro del árbol.
  • Ningún usuario tiene acceso a la contraseña de otro usuario.
  • Un usuario solo puede modificar sus propios datos.

Estos permisos son correctos en caso de tener un único árbol, pero en mi caso he separado en 3 entornos y no quiero que un usuario pueda ver nada de otro lugar, así que modificaré la seguridad para que cada usuario pueda ver únicamente su rama.

Aun con esta seguridad, todas las aplicaciones requieren definir un Base DN, en cada una se deberá indicar el correcto según su entorno para evitar que vean usuarios de otras ramas, y debido a que he utilizado los mismos nombres, podría fallar el inicio de sesión al encontrar múltiples usuarios con el mismo UID.

Primero necesitamos borrar los permisos actuales.

vi remove-acl.ldif
# {1}mdb, config
dn: olcDatabase={1}mdb,cn=config
delete: olcAccess

Creamos el fichero con los permisos.

vi acl.ldif

En este fichero volvemos a indicar los permisos originales añadiendo 3 nuevos, uno para cada rama, donde únicamente aquellos que pertenecen a esa rama tendrán acceso a ella.

acl.ldif
dn: olcDatabase={1}mdb,cn=config
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymous auth by * none
-
add: olcAccess
olcAccess: {1}to dn.subtree="ou=dev,dc=domain,dc=cat" by dn.children="ou=dev,dc=domain,dc=cat" read by * none
-
add: olcAccess
olcAccess: {2}to dn.subtree="ou=test,dc=domain,dc=cat" by dn.children="ou=test,dc=domain,dc=cat" read by * none
-
add: olcAccess
olcAccess: {3}to dn.subtree="ou=prod,dc=domain,dc=cat" by dn.children="ou=prod,dc=domain,dc=cat" read by * none
-
add: olcAccess
olcAccess: {4}to * by * read
-

Aplicamos el borrado y los nuevos permisos.

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f remove-acl.ldif
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f acl.ldif

Si necesitamos unos permisos diferentes, en la guía oficial está una extensa lista con las opciones que tenemos, también es aconsejable hacer una validación previa al fichero antes de aplicarlo:

ldapmodify -v -n -f <file.ldif>

Eliminar acceso anónimo

Por seguridad, el usuario anónimo suele ser una mala idea, es mejor que las aplicaciones utilicen un usuario de 'bind' para hacer las comprobaciones necesarias.

Además, si hemos aplicado la configuración descrita previamente, el usuario anónimo, al no pertenecer a ninguna rama, no podrá ver nada, algo que podemos sobrescribir, pero no vale la pena.

Igual que las anteriores veces, generamos el fichero de configuración.

vi anon.ldif
anon.ldif
dn: cn=config 
changetype: modify
add: olcDisallows
olcDisallows: bind_anon
-
add: olcRequires
olcRequires: authc

Aplicamos los cambios:

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f anon.ldif

Acceso admin

aviso

Aunque este proceso ha creado los usuarios de admin para cada entorno, estos no se diferencian del usuario de bind. La conversión a administradores únicamente de su entorno aún está en investigación, mientras se puede utilizar el admin principal limitando el Base DN.