Gestión centralizada de usuarios con OpenLDAP

En uno de los últimos artículos hablábamos de cómo podemos hacer que nuestra Ubuntu sea más segura siguiendo las buenas prácticas reconocidas por los estándares internacionales. Si bien en ese mismo texto explicábamos como gestionar los usuarios locales, no valoramos cómo realizar dicha gestión cuando disponemos de una red con más de un servidor.

Con el objetivo de tener un mayor control sobre los usuarios definidos y una política de contraseñas homogénea, lo ideal en una red es disponer de un servicio centralizado de autenticación. Imaginaros que disponemos de una red con 20 servidores y necesitásemos dar de alta un nuevo usuario, deberíamos ejecutar las pertinentes modificaciones en los 20 diferentes entornos con el riesgo de errores e inconsistencias que ésto implica. Por otra parte, desde la perspectiva del usuario nos encontramos con 20 contraseñas que no tienen porque estar sincronizadas, diferentes políticas, etc… al final es más que probable que el usuario utilice contraseñas débiles o que las anote en un postit pegado al monitor.

Por esos motivos, desde el punto de vista de la seguridad, es recomendable disponer de un servicio centralizado de autenticación. Históricamente en el mundo Unix se ha utilizado NIS (Network Information Service), pero actualmente ya se encuentra en desuso y se recomienda LDAP por tratarse de un sistema más moderno y seguro.

Entre las implementaciones de LDAP más conocidas tenemos el Directorio Activo de Windows y OpenLDAP para sistemas Linux/Unix (también puede integrarse con clientes Windows). Veamos como podemos montar un servicio centralizado de autenticación con OpenLDAP que cumpla los siguientes requisitos:

  • Autenticación contra un único punto centralizado de la red.
  • Conexiones cifradas con TLS.
  • Identificación de estaciones mediante certificados digitales (únicamente podrán conectarse al servidor los clientes que dispongan de un certificado creado por nosotros y viceversa)
  • Política de contraseñas homogénea y robusta.
  • Los usuarios definidos en OpenLDAP dispondrán de campos donde se especificará a que máquinas tienen acceso. La autorización a que servicios de cada máquina se delegará a cada servidor para no perder flexibilidad.
  • Cuando el usuario se autentica por primera vez en un sistema, se almacenan sus credenciales en una cache local, de forma que si el servidor de LDAP cae temporalmente, el usuario puede seguir conectándose.

Entidad certificadora SSL

Cómo comentabamos en la introducción, vamos a configurar LDAP para que se realicen conexiones cifradas. Para ésto tenemos la opción de generar un certificado SSL auto-firmado:

mkdir /etc/ldap/ssl
cd /etc/ldap/ssl
openssl req -newkey rsa:1024 -x509 -nodes -out server.pem -keyout server.pem -days 3650

Con este certificado únicamente podemos cifrar las conexiones pero no identificar los clientes o el servidor. Por tanto, cualquiera ordenador de la red puede conectarse al servidor sin que éste pueda verificar si se trata de un cliente autorizado, o a la inversa, los clientes no pueden verificar que se estan conectando al servidor autorizado y por ejemplo podrían enviar información confidencial (p.ej. contraseñas).

Para evitar esa situación, la configuración más segura consiste en crear nuestra propia Autoridad Certificadora (CA) y emitir tantos certificados como necesitamos (1 por cliente + el servidor) firmados por la misma. Así podremos especificar que el servidor y el cliente solo acepten conexiones con certificados firmados por nuestra Autoridad Certificadora:

mkdir /etc/ssl/marblestationCA
mkdir /etc/ssl/marblestationCA/certs
mkdir /etc/ssl/marblestationCA/private

echo "01" > /etc/ssl/marblestationCA/serial
touch /etc/ssl/marblestationCA/index.txt

Creamos “/etc/ssl/marblestationCA/caconfig.cnf”:

# My sample caconfig.cnf file.
#
# Default configuration to use when one is not provided on the command line.
#
[ ca ]
default_ca      = local_ca
#
#
# Default location of directories and files needed to generate certificates.
#
[ local_ca ]
dir             = /etc/ssl/marblestationCA
certificate     = $dir/cacert.pem
database        = $dir/index.txt
new_certs_dir   = $dir/certs
private_key     = $dir/private/cakey.pem
serial          = $dir/serial

#       
#
# Default expiration and encryption policies for certificates.
#
default_crl_days        = 3650
default_days            = 1825
default_md              = md5
#       
policy          = local_ca_policy
x509_extensions = local_ca_extensions
#       
#
# Default policy to use when generating server certificates.  The following
# fields must be defined in the server certificate.
#
[ local_ca_policy ]
commonName              = supplied
stateOrProvinceName     = supplied
countryName             = supplied
emailAddress            = supplied
organizationName        = supplied
#       
#
# x509 extensions to use when generating server certificates.
#
[ local_ca_extensions ]
#subjectAltName          = DNS:host.ejemplo.com,DNS:xenhost.ejemplo.com
basicConstraints        = CA:false
#nsCertType              = server
#       
#
# The default root certificate generation policy.
#
[ req ]
default_bits    = 2048
default_keyfile = /etc/ssl/marblestationCA/private/cakey.pem
default_md      = md5
#       
prompt                  = no
distinguished_name      = root_ca_distinguished_name
x509_extensions         = root_ca_extensions
#
#
# Root Certificate Authority distinguished name.  Change these fields to match
# your local environment!
#
[ root_ca_distinguished_name ]
commonName              = Marble Station Root Certificate Authority
stateOrProvinceName     = Catalunya
countryName             = ES
emailAddress            = root@host.ejemplo.com
organizationName        = Marble Station
organizationalUnitName  = Host
#       
[ root_ca_extensions ]
basicConstraints        = CA:true

A continuación creamos las claves que usará el CA, la contraseña que establezcamos ahora será solicitada cada vez que queramos firmar un nuevo certificado:

OPENSSL_CONF=/etc/ssl/marblestationCA/caconfig.cnf openssl req -x509 -newkey rsa:2048 -out cacert.pem -outform PEM -days 3650

Este comando generará el certificado “/etc/ssl/marblestationCA/cacert.pem” y la clave privada de CA “/etc/ssl/marblestationCA/private/cakey.pem”.

A continuación crearemos una clave para el servidor que ofrece el servicio LDAP y otro para un cliente, para ello creamos el fichero “/etc/ssl/marblestationCA/host-server.cnf”:

#
# desktop-server.cnf
#

[ req ]
prompt                  = no
distinguished_name      = server_distinguished_name

[ server_distinguished_name ]
commonName              = host.ejemplo.com
stateOrProvinceName     = Catalunya
countryName             = ES
emailAddress            = root@host.ejemplo.com
organizationName        = Marble Station
organizationalUnitName  = Host

NOTA: Es muy importante tener en cuenta que el dominio que indiquemos en “commonName” debe coincidir con el dominio de la máquina que va a utilizar el certificado, de lo contrario las conexiones serán rechazadas. Por ejemplo, algo tan sencillo como un servidor con 2 dominios ‘uno.com’ y ‘dos.com’ apuntando a la misma IP, si el certificado se genera indicando ‘uno.com’, los clientes que se conecten deben hacerlo mediante ese dominio y no ‘dos.com’. A pesar de apuntar a la misma IP, OpenLDAP verificará el dominio utilizado.

Generamos la clave sin contraseña (argumento “-nodes”) dado que de lo contrario el servicio no podría arrancar de forma automática:

OPENSSL_CONF=/etc/ssl/marblestationCA/host-server.cnf openssl req -newkey rsa:1024 -keyout host-server_key.pem -keyform PEM -out host-server_req.pem -outform PEM -nodes

Utilizamos la petición de firma para el CA que acabamos de crear (“host-server_req.pem”) para generar el certificado final firmado:

OPENSSL_CONF=/etc/ssl/marblestationCA/caconfig.cnf openssl ca -in host-server_req.pem -out host-server_crt.pem

A continuación podemos repetir el proceso para generar más certificados firmados por el CA, creando un nuevo fichero de configuración (p.ej. “/etc/ssl/marblestationCA/desktop-server.cnf”) y repitiendo los pasos anteriores:

OPENSSL_CONF=/etc/ssl/marblestationCA/desktop-server.cnf openssl req -newkey rsa:1024 -keyout desktop-server_key.pem -keyform PEM -out desktop-server_req.pem -outform PEM -nodes
OPENSSL_CONF=/etc/ssl/marblestationCA/caconfig.cnf openssl ca -in desktop-server_req.pem -out desktop-server_crt.pem

Servidor OpenLDAP

En el apartado anterior hemos preparado los certificados que vamos a utilizar, ahora ha llegado la hora de instalar el servidor OpenLDAP:

apt-get install slapd ldap-utils db4.2-util
apt-get install libpam-ldap

Los certificados de la anterior sección, concretamente el certificado que será utilizado por el servidor y el certificado del CA los copiaremos a su lugar correspondiente:

sudo -s
mkdir /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/host-server_key.pem /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/host-server_crt.pem /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/cacert.pem /etc/ldap/ssl/
chown openldap:openldap /etc/ldap/ssl/*
chmod 640 /etc/ldap/ssl/*

Copiamos el esquema que nos permitirá indicar a que hosts se pueden conectar los usuarios (atributos ‘host’, que pueden contener el hostname de las máquinas o un ‘*’ para permitir acceso a todas):

cp /usr/share/doc/libpam-ldap/ldapns.schema /etc/ldap/schema/

A continuación editamos la configuración del servidor ‘/etc/ldap/slapd.conf’:

# Schema and objectClass definitions
include         /etc/ldap/schema/core.schema
include         /etc/ldap/schema/cosine.schema
include         /etc/ldap/schema/nis.schema
include         /etc/ldap/schema/inetorgperson.schema

include         /etc/ldap/schema/misc.schema
include         /etc/ldap/schema/ppolicy.schema
include         /etc/ldap/schema/ldapns.schema

# Where the pid file is put. The init.d script
# will not stop the server if you change this.
pidfile         /var/run/slapd/slapd.pid

# List of arguments that were passed to the server
argsfile        /var/run/slapd/slapd.args

# Read slapd.conf(5) for possible values
loglevel        none

# Where the dynamically loaded modules are stored
modulepath  /usr/lib/ldap
moduleload  back_hdb
moduleload ppolicy.la

# The maximum number of entries that is returned for a search operation
sizelimit 500

# The tool-threads parameter sets the actual amount of cpu's that is used
# for indexing.
tool-threads 1

backend     hdb
database        hdb

# The base of your directory in database #1
suffix          "dc=marblestation,dc=com"

overlay ppolicy
ppolicy_default "cn=Standard,ou=Policies,dc=marblestation,dc=com"
ppolicy_use_lockout

# rootdn directive for specifying a superuser on the database. This is needed
# for syncrepl.
rootdn          "cn=admin,dc=marblestation,dc=com"

# Where the database file are physically stored for database #1
directory       "/var/lib/ldap"

index   uid eq

# For the Debian package we use 2MB as default but be sure to update this
# value if you have plenty of RAM
dbconfig set_cachesize 0 2097152 0

# Sven Hartge reported that he had to set this value incredibly high
# to get slapd running at all. See http://bugs.debian.org/303057 for more
# information.

# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500

# Indexing options for database #1
index           objectClass eq

# Save the time that the entry gets modified, for database #1
lastmod         on

# Checkpoint the BerkeleyDB database periodically in case of system
# failure and to speed slapd shutdown.
checkpoint      512 30

# The userPassword by default can be changed
# by the entry owning it if they are authenticated.
# Others should not be able to see it, except the
# admin entry below
# These access lines apply to database #1 only
# #,shadowLastChange
access to attrs=userPassword
        by dn="cn=admin,dc=marblestation,dc=com" write
        by anonymous auth
        by self write
        by * none

access to attrs=shadowLastChange
  by self write
  by * read

# Ensure read access to the base for things like
# supportedSASLMechanisms.  Without this you may
# have problems with SASL not knowing what
# mechanisms are available and the like.
# Note that this is covered by the 'access to *'
# ACL below too but if you change that as people
# are wont to do you'll still need this if you
# want SASL (and possible other things) to work 
# happily.
access to dn.base="" by * read

# The admin dn has full write access, everyone else
# can read everything.
access to *
        by dn="cn=admin,dc=marblestation,dc=com" write
        by * read

TLSCertificateFile /etc/ldap/ssl/host-server_crt.pem
TLSCertificateKeyFile /etc/ldap/ssl/host-server_key.pem
TLSCACertificateFile /etc/ldap/ssl/cacert.pem
TLSVerifyClient demand

En este fichero, respecto al original, se han realizado la siguientes modificaciones:

  • Se han añadido nuevos esquemas mediante el comando ‘include’.
  • Se realiza la carga del módulo ‘ppolicy’ y se establece la configuración básica.
  • Se especifica el sufijo que será utilizado para la identificación de los datos que contendrá LDAP (“dc=marblestation,dc=com”)
  • Se establece el usuario administrador mediante el comando ‘rootdn’ (teniendo en cuenta el mismo sufijo que hemos especificado)
  • Se configuran los certificados TLS para que estos sean requeridos y validados.

Por otra parte, en el fichero ‘/etc/default/slapd’ activaremos el servicio SSL y Unix sockets:

SLAPD_SERVICES="ldaps:/// ldapi:///"

Con esto ya podemos reiniciar nuestro servidor OpenLDAP:

/etc/init.d/slapd restart

Ahora nos falta definir la estructura base de los datos internos.

Estructura del directorio

La información en LDAP se almacena mediante objetos que son construidos a partir de clases definidas en los esquemas (éstos se cargan automáticamente en base a las ‘includes’ del fichero de configuración de slapd). Veamos una definición de ejemplo:

dn: cn=admin,dc=marblestation,dc=com
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: admin
description: LDAP administrator
userPassword: {SSHA}HHWw45HYVamEIUFHp1emGejTkbY1bntB

Elementos de la definición anterior:

  • En primer lugar tenemos el nombre unívoco (Distinguished Name) del objeto: “cn=admin,dc=marblestation,dc=com”. Siempre acompañado del sufijo que hemos especificado en la configuración (en este caso “dc=marblestation,dc=com”).
  • El objeto tendrá los atributos de las clases ‘organizationalRole’ y ‘simpleSecurityObject’. Estas clases se encuentran definidos en los esquemas ‘/etc/ldap/schema/’ que son cargados por LDAP al iniciarse. Un objeto puede estar compuesto por tantas clases auxiliares (p.ej. simpleSecurityObject) y abstractas (p.ej. top) como se requiera pero solo puede haber una de tipo estructural (organizationalRole en este caso).
  • Finalmente se listan los atributos, en este caso ‘cn’, ‘description’ y ‘userPassword’.

Todos los objetos en LDAP siguen siempre esa estructura, la cual dista bastante de las típicas bases de datos relacionales (SQL). Otra diferencia importante respecto a esas BBDD es que LDAP está optimizado para realizar consultas de lectura muy rápidas, por eso LDAP se utiliza en entornos que utilizan datos que no van a ser modificados frecuentemente (a diferencia de una BBDD relacional como MySQL).

Pasemos a construir la estructura básica de nuestro servidor LDAP, nos situamos en un directorio temporal de trabajo y creamos el fichero ‘structure.ldif’:

##### Raiz
dn: dc=marblestation,dc=com
objectClass: dcObject
objectClass: organizationalUnit
dc: marblestation
ou: marblestation Dot Com

##### Administrador de LDAP
dn: cn=admin,dc=marblestation,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword: {SSHA}HHWw45HYVamEIUFHp1emGejTkbY1bntB
## Password genered with slappasswd

##### Usuarios
dn: ou=People,dc=marblestation,dc=com
objectClass: organizationalUnit
ou: people

##### Grupos
dn: ou=Group,dc=marblestation,dc=com
objectClass: organizationalUnit
ou: group

##### Politicas de contraseñas
dn: ou=Policies,dc=marblestation,dc=com
objectClass: organizationalUnit
ou: policies

En ese fichero hemos definido la raiz donde colgarán todos los datos, el administrador del sistema LDAP y unidades organizativas donde añadiremos los usuarios, los grupos y las políticas de contraseña.

Cabe destacar que todas las contraseñas que necesitemos para definir la estructura las generaremos mediante el comando ‘slappasswd’ (por ejemplo, la del usuario administrador anterior):

$ slappasswd
New password:
Re-enter password:
{SSHA}d2BamRTgBuhC6SxC0vFGWol31ki8iq5m

Continuemos con el contenido del servidor LDAP. Añadiremos una política de contraseñas que definiremos en el fichero ‘policy.ldif’:

##### Política estándard
dn: cn=Standard,ou=Policies,dc=marblestation,dc=com
objectClass: top
objectClass: device
objectClass: pwdPolicy
cn: Standard
# Field were password is stored:
pwdAttribute: userPassword
#### Expiration rules
# Number of seconds that must elapse between modifications allowed to  the  password:
# 1 day
pwdMinAge: 86400
# 60 days
pwdMaxAge: 5184000
# 7 days
pwdExpireWarning: 604800
# if its value is zero (0), users  with  expired  passwords will not be allowed to authenticate to the directory.
pwdGraceAuthnLimit: 0
# User must change its password after an admin has set it
pwdMustChange: TRUE
# Users can change their password
pwdAllowUserChange: TRUE
# It is needed the old password to set a new one
# NOTA: Si lo activamos no podremos cambiar la contraseña mediante pam_ldap
pwdSafeModify: FALSE
##### Password quality
# Maximum number of used passwords that will be stored in the pwdHistory attribute:
pwdInHistory: 12
# If its value is one (1), the server will check  the  syntax,  and  if  the
# server  is  unable  to  check  the syntax, whether due to a client-side
# hashed password or some other reason, it will be accepted. If its value
# is  two  (2),  the  server  will check the syntax, and if the server is
# unable to check the syntax it will return an error refusing  the  password.
pwdCheckQuality: 2
# Minimum  number  of  characters
pwdMinLength: 8
##### Automatic locks
# Lock user accounts after 5 failed authentications
pwdMaxFailure: 5
pwdLockout: TRUE
# 10 minutes
pwdLockoutDuration: 600
# if its value is zero (0), the failure counter will only be reset by a successful authentication
pwdFailureCountInterval: 0

Ésta política será la que aplicará a todos los usuarios (excepto al administrador LDAP) y el propio fichero es auto-explicativo.

Vamos a crear un usuario de prueba en el fichero ‘users.ldif’:

dn: uid=marble,ou=people,dc=marblestation,dc=com
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
objectClass: inetLocalMailRecipient
objectClass: hostObject
objectClass: authorizedServiceObject
#objectClass: kerberosSecurityObject
#######################################
### Posix Account
uid: marble
cn: Sergi Blanco Cuaresma
gecos: Sergi Blanco Cuaresma
uidNumber: 1000
gidNumber: 1000
loginShell: /bin/bash
homeDirectory: /home/marble
#######################################
### Password
userPassword: {SSHA}PFIJb4fTH4klVKd5nE4J0WDUs3Rtmxtd
pwdReset: TRUE
#pwdPolicySubEntry: cn=Standard,ou=Policies,dc=marblestation,dc=com
#pwdPolicySubEntry: cn=No Policy, ou=Policies, dc=example, dc=com
## Discarted
#shadowExpire: -1
#shadowFlag: 0
#shadowMin: 1
shadowWarning: 7
shadowMax: 60
shadowLastChange: 0
#shadowMax: 99999
#shadowLastChange: 14156
#######################################
### Kerberos
#krbname: marble@EJEMPLO.COM
#######################################
### Authorization (e.g. host: desktop, authorizedService: sshd)
host: *
authorizedService: *
### Mail
mailRoutingAddress: marble@host.ejemplo.com
mailHost: host.ejemplo.com

Ésta sera la definición habitual de los usuarios de los sistemas, la mayoría de atributos corresponden con la información que habitualmente se almacena en sistemas Unix. Adicionalmente, tenemos otros atributos como ‘host’, que define a que máquinas podrá conectarse (el ‘*’ significa “a todas”, si quisiéramos especificar máquinas tendríamos que repetir el atributo tantas veces como máquinas queramos especificar).

Cabe destacar que utilizaremos los atributos shadow para replicar la política global que hemos establecido con ‘ppolicy’. Si bien podríamos ignorarlos, no nos cuesta nada indicar la misma política de contraseña para asegurarnos que se cumple bajo cualquier circunstancia (p.ej. si tenemos conexiones SSH con autenticación por claves RSA/DSA, PAM únicamente comprueba los campos shadow y por tanto ‘ppolicy’ no tiene efecto hasta que el usuario no realiza una conexión utilizando su clave normal).

Finalmente nos falta definir los grupos del sistema, añadamos un par de ejemplo en un fichero ‘groups.ldif’:

dn: cn=marble,ou=group,dc=marblestation,dc=com
objectClass: posixGroup
cn: marble
gidNumber: 1000

dn: cn=users,ou=group,dc=marblestation,dc=com
objectClass: posixGroup
cn: users
memberUid: marble
gidNumber: 100

Ahora que ya tenemos todos los ficheros con la estructura y los datos iniciales, ha llegado el momento de cargarlos en el servidor. Para ello borraremos todos los datos actuales de LDAP (no hemos introducido nada todavia, por tanto no debería haber nada de valor) y cargamos la información:

sudo /etc/init.d/slapd stop
sudo rm -rf /var/lib/ldap/*
sudo slapadd -l structure.ldif
sudo slapadd -l policy.ldif
sudo slapadd -l users.ldif
sudo slapadd -l groups.ldif
sudo chown -R openldap:openldap /var/lib/ldap
sudo /etc/init.d/slapd start

Si todo ha ido bien, ya tenemos nuestro servidor LDAP en marcha con la estructura base y un usuario de prueba.

Consultas locales a OpenLDAP

Para poder hacer consultas desde el propio servidor donde hemos instalado LDAP editamos ‘/etc/ldap/ldap.conf’:

BASE    dc=marblestation,dc=com
URI ldapi:///

De esta forma los comandos ‘ldap’ se conectaran al servidor utilizando Unix sockets y no necesitaremos configurar TLS dado que los datos no viajan por ninguna red.

Podemos probar a realizar una consulta conectando como el administrador de LDAP (nos solicitará la contraseña que le hayamos asignado en la definición del mismo) y preguntando sobre los objetos con el atributo ‘uid=marble’:

ldapsearch -x -D "cn=admin,dc=marblestation,dc=com" -W uid=marble

Si queremos evitar tener que escribir siempre el nombre del administrador, podemos crear el archivo ‘~/.ldaprc’:

BASE    dc=marblestation,dc=com
URI ldapi:///
BINDDN  cn=admin,dc=marblestation,dc=com

Así simplemente bastará con ejecutar:

ldapsearch -x -W uid=marble

Administración LDAP y gestión de usuarios

Comandos LDAP

OpenLDAP proporciona diversos comandos para la administración del servicio:

    ldapdelete: Borra entradas.
    ldapmodrdn: Modifica RDN (Relative Distinguised Names), por ejemplo permite mover “uid=marble,ou=people,dc=marblestation,dc=com” a “uid=marble,ou=gente,dc=marblestation,dc=com”
    ldapsearch: Realiza consultas.
    ldapcompare: Comparaciones.
    ldapmodify: Permite añadir entradas o realizar modificaciones.
    ldappasswd: Establece la contraseña del usuario.
    ldapwhoami: Indica el usuario con el que nos conectamos a LDAP.

Su uso no es sencillo y para aprender a utilizarlas lo mejor es consultar sus respectivos manuales. No obstante, disponemos de otras interfícies que nos facilitaran la gestión de LDAP.

phpLDAPadmin

Para la administración general del LDAP, si tenemos ya configurado Apache2, una de las opciones más cómodas es la instalación de phpldapadmin:

apt-get install phpldapadmin

Editamos ‘/etc/phpldapadmin/config.php’ y establecemos los siguientes parámetros (acceso local por Unix sockets, etc.):

...
$ldapservers->SetValue($i,'server','host','ldapi://');
...
$ldapservers->SetValue($i,'server','base',array('dc=marblestation,dc=com'));
...
$ldapservers->SetValue($i,'login','dn','cn=admin,dc=marblestation,dc=com');
...

Podremos acceder a la aplicación mediante ‘http://localhost/phpldapadmin/’ y desde allí la aplicación nos permitirá realizar consultar, modificaciones, duplicar objetos, etc.

CPU – Change Passowrd Utility

Si bien con phpLDAPadmin podremos realizar casi cualquier operación sobre LDAP vía web, CPU nos proporciona comandos de consola análogos a los tradicionales ‘useradd’, ‘usermod’, ‘userdel’, ‘groupadd’, etc. Ésto puede facilitar al administrador la tarea de gestión de usuarios:

apt-get install cpu

Editamos ‘/etc/cpu/cpu.conf’ para establecer las opciones de conexión (vía local por Unix Socket), usuario administrador LDAP (no indicamos la contraseña, haremos que nos la pregunte cada vez que ejecutemos cpu) y otros parámetros:

# See cpu.conf(5) for documentation

[GLOBAL]
DEFAULT_METHOD  = ldap
CRACKLIB_DICTIONARY = /var/cache/cracklib/cracklib_dict

[LDAP]
LDAP_URI                = ldapi:///
BIND_DN                 = "cn=admin,dc=marblestation,dc=com"
BIND_PASS               = ""
USER_BASE               = "ou=People,dc=marblestation,dc=com"
GROUP_BASE              = "ou=Group,dc=marblestation,dc=com"
USER_OBJECT_CLASS       = account,posixAccount,shadowAccount,top,inetLocalMailRecipient,hostObject,authorizedServiceObject
#kerberosSecurityObject
GROUP_OBJECT_CLASS  = posixGroup,top
USER_FILTER = (objectClass=posixAccount)
GROUP_FILTER    = (objectClass=posixGroup)
USER_CN_STRING  = uid
GROUP_CN_STRING = cn
SKEL_DIR    = /etc/skel
DEFAULT_SHELL   = /bin/bash
HOME_DIRECTORY  = /home
MAX_UIDNUMBER = 10000
MIN_UIDNUMBER = 1000
MAX_GIDNUMBER = 10000
MIN_GIDNUMBER = 1000
ID_MAX_PASSES = 1000
# Whether each user should have its own group created or not
USERGROUPS = yes
# If you change usergroup set this to the default group a user should have
#USERS_GID = 100
RANDOM = "false"
PASSWORD_FILE = "/etc/passfile"
SHADOW_FILE = "/etc/shadowfile"
HASH = "clear"
SHADOWLASTCHANGE    = 11192
SHADOWMAX       = 99999
SHADOWWARING        = 7
SHADOWEXPIRE        = -1
SHADOWFLAG      = 134538308
SHADOWMIN       = -1
SHADOWINACTIVE      = -1

Adicionalmente, crearemos el fichero ‘/etc/cpu/attributes.ldif’:

pwdReset: TRUE
host: *
authorizedService: *

De esta forma, cuando creemos usuarios se añadirán los atributos anteriores por defecto (el usuario esta obligado a resetear la contraseña y tiene acceso a todos los sistemas). Hagamos una prueba añadiendo un nuevo usuario (la primera contraseña que nos solicita corresponde a la del administrador LDAP):

cpu -w -a /etc/cpu/attributes.ldif useradd -p test01

El anterior comando generará el usuario ‘test01’ y el grupo ‘test01’.

Si queremos listar los usuarios definidos:

cpu -w cat

Bloqueo y desbloqueo de usuarios:

cpu -w usermod -L test01
cpu -w usermod -U test01

Borrado de usuarios:

cpu -w userdel prueba22

Para consultar el listado completo de comandos que podemos ejecutar con ‘cpu’ podemos consultar el manual:

man cpu-ldap

Clientes OpenLDAP

A continuación vamos a configurar una máquina que se conectará contra el servidor OpenLDAP para autenticar a sus usuarios:

apt-get install libpam-ldap libnss-ldap nss-updatedb libnss-db ldap-utils libpam-ccreds

Para la conexión, necesitaremos disponer de un certificado firmado por la CA que hemos generado tal y como se especificaba al inicio del artículo. Una vez generados los certificados (clave privada, clave pública firmada y certificado CA), los copiaremos mediante sftp y los moveremos al lugar correspondiente:

sudo -s
mkdir /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/desktop-server_key.pem /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/desktop-server_crt.pem /etc/ldap/ssl/
cp /etc/ssl/marblestationCA/cacert.pem /etc/ldap/ssl/
chmod 644 /etc/ldap/ssl/*
chown root:root /etc/ldap/ssl/*

A continuación vamos a configurar LDAP como cliente, editamos ‘/etc/ldap/ldap.conf’:

BASE    dc=marblestation,dc=com
URI     ldaps://host.ejemplo.com

TLS_CACERT /etc/ldap/ssl/cacert.pem
TLS_REQCERT demmand
#TLS_REQCERT never # Useful to check if "Can't contact LDAP server" because of certificate problems

Utilizando el usuario que vayamos a utilizar para conectarnos al servidor, creamos ‘~/.ldaprc’:

 
BASE    dc=marblestation,dc=com
URI     ldaps://host.ejemplo.com
BINDDN  cn=admin,dc=marblestation,dc=com

TLS_CACERT /etc/ldap/ssl/cacert.pem
TLS_REQCERT demmand
#TLS_REQCERT never # Useful to check if "Can't contact LDAP server" because of certificate problems

#### User only (need to copy /etc/ldap/ldap.conf to ~/.ldaprc)
# Client certificate and key
# Use these, if your server requires client authentication.
TLS_CERT /etc/ldap/ssl/desktop-server_crt.pem
TLS_KEY /etc/ldap/ssl/desktop-server_key.pem

En esos dos ficheros hemos especificado:

  • El nombre base “dc=marblestation,dc=com”.
  • El servidor al que nos vamos a conectar y el protocolo que vamos a utilizar, en este caso nos conectaremos mediante SSL al puerto 636 LDAP del servidor ‘host.ejemplo.com’. Es muy importante que este dominio coincida con el ‘commonName’ especificado en el servidor al cual nos vamos a conectar, de lo contrario la conexión no se establecerá.
  • El usuario con el que vamos a realizar la conexión (corresponde al administrador).
  • Certificado privado, público firmado y CA, así como la solicitud de validación del certificado del servidor.

A continuación podemos probar la conexión realizando una consulta al servidor:

ldapsearch -x -W uid=marble

Ahora que ya hemos podido comprobar que podemos conectarnos sin problemas desde el cliente, vamos a configurar el sistema para que autentique los usuarios contra LDAP.

Lo primero será editar el fichero ‘/etc/ldap.conf’ para establecer los parámetros de conexión utilizados por PAM (sistema habitual de autenticación):

# Your LDAP server. Must be resolvable without using LDAP.
uri ldaps://host.ejemplo.com

# The distinguished name of the search base.
base dc=marblestation,dc=com

ldap_version 3

# The distinguished name to bind to the server with
# if the effective user ID is root. Password is
# stored in /etc/ldap.secret (mode 600)
# NOTA: No activar o dejará de ser efectiva la politica de contraseñas (ppolicy)
#rootbinddn cn=admin,dc=marblestation,dc=com

# Search timelimit
timelimit 5

# Bind/connect timelimit
bind_timelimit 5

# Reconnect policy: hard (default) will retry connecting to
# the software with exponential backoff, soft will fail
# immediately.
bind_policy soft

# Filter to AND with uid=%s
#pam_filter objectclass=posixAccount
#pam_filter gidNumber=1000

# check for login rights on the host
pam_check_host_attr yes
#pam_filter (&(objectClass=posixAccount)(authorizedService=site-admin))
#pam_filter |(host=desktop)(host=\*)

#pam_check_service_attr yes

pam_lookup_policy yes

# Specify a minium or maximum UID number allowed
#pam_min_uid 1000
#pam_max_uid 0

# Do not hash the password at all; presume
# the directory server will do it, if
# necessary. This is the default.
#pam_password md5
pam_password clear

# OpenLDAP SSL mechanism
# start_tls mechanism uses the normal LDAP port, LDAPS typically 636
ssl start_tls
ssl on

# OpenLDAP SSL options
# Require and verify server certificate (yes/no)
tls_checkpeer yes
# tls_checkpeer no # Useful to check if "Can't contact LDAP server" because of certificate problems

# CA certificates for server certificate verification
#tls_cacertdir /etc/ssl/certs
tls_cacertfile /etc/ldap/ssl/cacert.pem

# Client certificate and key
tls_cert /etc/ldap/ssl/desktop-server_crt.pem
tls_key /etc/ldap/ssl/desktop-server_key.pem


use_sasl off
rootuse_sasl off
sasl_secprops maxssf=0
idle_timelimit 3600
nss_reconnect_tries 1
nss_reconnect_sleeptime 1
nss_reconnect_maxsleeptime 8
nss_reconnect_maxconntries 2
nss_paged_results yes
nss_base_passwd ou=people,dc=marblestation,dc=com?one
nss_base_shadow ou=people,dc=marblestation,dc=com?one
nss_base_group ou=group,dc=marblestation,dc=com?one

nss_initgroups_ignoreusers avahi,avahi-autoipd,backup,bin,chipcard,daemon,dhcp,ftp,games,gdm,gnats,haldaemon,hplip,irc,klog,landscape,libuuid,list,lp,mail,man,messagebus,news,nx,polkituser,proxy,pulse,root,snmp,sshd,sync,sys,syslog,uucp,www-data

En este fichero hemos especificado:

  • Host al que nos conectaremos. Como siempre, es importante que coincida con el ‘commonName’ del certificado del servidor, de lo contrario la conexión no se establecerá.
  • Opciones de conexión varias (el puerto 636 corresponde a ldaps://).
  • Configuraciones PAM:
    • Validación del atributo ‘host’. Si el usuario dispone de un atributo ‘host’ con valor ‘*’ o con el nombre de la máquina (debe coincidir con el resultado del comando ‘hostname’), entonces podrá logearse en el sistema. De lo contrario, se deniega el acceso.
    • Validación de la política de contraseñas.
    • Envío de contraseñas en claro. Este requisito es necesario para que la política de contraseñas pueda ser validada en el servidor y por otra parte, tampoco supone un riesgo de seguridad dado que la conexión será cifrada.
  • Configuración de los certificados a utilizar.
  • Rutas donde encontrar la información de usuarios, grupos, etc.

A continuación vamos a preparar el sistema para que si perdemos conectividad, podamos seguir asociando uid y gid con los nombres asignados. Para esto se copiaran todos los usuarios y grupos (solo uid/gid y nombres) en local mediante el comando:

sudo nss_updatedb ldap

Es necesario que esta actualización se haga de forma periódica mediante cron (al menos 1 vez al día) por si hay cambios en el servidor:

echo '#!/bin/sh'               | sudo tee    /etc/cron.daily/upd-local-nss-db
echo `which nss_updatedb` ldap | sudo tee -a /etc/cron.daily/upd-local-nss-db
sudo chmod +x /etc/cron.daily/upd-local-nss-db

En este punto estamos preparados para indicarle al sistema de donde debe obtener la información sobre usuarios, contraseñas y grupos, para ello editamos ‘/etc/nsswitch.conf’ y cambiamos:

#passwd:         compat
#group:          compat
#shadow:         compat

Por:

passwd:         files ldap [NOTFOUND=return] db
group:          files ldap [NOTFOUND=return] db
shadow:     files ldap

En estas líneas estamos indicando que el sistema debe buscar la información de usuarios y grupos primero en los archivos locales (/etc/passwd y /etc/group) y después en LDAP, pero en caso de que este último falle entonces debe consultar la copia local que hemos realizado con ‘nss_updatedb’. En el caso de las contraseñas, únicamente consultará el fichero local (‘/etc/shadow’) y el servidor (el cacheo de credenciales lo realizaremos por otra vía).

Con esta configuración tendremos máxima flexibilidad dado que podremos optar por tener usuario/grupos locales y usuarios/grupos centralizados de forma simultanea. O bien, podemos migrar todas las cuentas a LDAP y dejar en blanco los archivos locales.

Para validar la configuración, podemos comprobar el listado de usuarios/grupos actuales de nuestro sistema (combinará los definidos localmente con los usuarios creados en LDAP):

sudo getent passwd
sudo getent group

Finalmente nos falta configurar los archivos de autenticación PAM.

NOTA: Si estamos accediendo remotamente al servidor que estamos modificando, es importante tener en cuenta que estos cambios pueden bloquearnos futuros accesos y por tanto debemos probar cada cambio que hagamos.

Comenzaremos editando ‘/etc/pam.d/common-auth’ para que quede con el siguiente contenido:

auth    [success=done default=ignore]   pam_unix.so nullok_secure try_first_pass
# If LDAP is unavailable, go to next line.  If authentication via LDAP is successful, skip 1 line.
# If LDAP is available, but authentication is NOT successful, skip 2 lines.
auth    [authinfo_unavail=ignore success=1 default=2] pam_ldap.so use_first_pass
auth    [default=done]  pam_ccreds.so action=validate use_first_pass
auth    [default=done]  pam_ccreds.so action=store
auth    [default=bad]   pam_ccreds.so action=update

En este fichero hemos indicado que primero debe intentar autenticar los usuarios utilizando la información local, de lo contrario trata de autenticar contra el servidor LDAP (utilizando la configuración ‘/etc/ldap.conf’ que ya hemos preparado). Si este último falla, comprobará si las credenciales se encuentran cacheadas en local e intentará autenticarlo (antepenúltima línea).

Por otra parte, cada vez que se realice una autenticación exitosa contra LDAP, se ejecutará la penúltima línea y se guardarán las credenciales en local (concretamente en /var/cache/.security.db). En caso que la cuenta haya sido bloqueada/eliminada de LDAP, se ejecutará la última línea con la cual se borraran las credenciales cacheadas del usuario.

Editemos ahora ‘/etc/pam.d/common-account’ para que quede con el siguiente contenido:

account     [user_unknown=ignore authinfo_unavail=ignore default=done] pam_ldap.so
account     [user_unknown=ignore authinfo_unavail=ignore default=done] pam_unix.so
account     required       pam_permit.so

Editamos ‘/etc/pam.d/common-session’ y lo dejamos así:

session    required     pam_limits.so
#session    required     pam_mkhomedir.so skel=/etc/skel/
session    required     pam_unix.so
session    optional     pam_ldap.so

Editamos ‘/etc/pam.d/common-password’ para que contenga:

password   required   pam_ldap.so ignore_unknown_user
password   optional   pam_unix.so shadow md5 try_first_pass

A continuación podemos probar a autenticarnos en el sistema (login normal, ssh o ‘su – usuario’) utilizando usuarios locales y usuarios definidos en LDAP (p.ej. el que hemos creado de prueba).

Cuando conectemos con el usuario de LDAP, las credenciales han debido ser cacheadas y podemos validarlo mediante:

sudo cc_dump
  Credential Type  User             Service  Cached Credentials
  ----------------------------------------------------------------------------------
  Salted SHA1      fred             any     4a985b233701cf106ed450a0168fa8e0aa86ef5d

En caso de que queramos borrar manualmente alguna credencial cacheada, podemos ejecutar:

sudo cc_test -update any usuario -

Con esto ya tenemos montado el sistema de autenticación centralizado cliente-servidor 🙂

8 thoughts on “Gestión centralizada de usuarios con OpenLDAP

  1. calla, calla, aún en las noches de tormenta tengo pesadillas con la configuración, la estabilidad y la integración del openldap usando mysql de backend…como escarpias tengo los pelos sólo de recordarlo…

  2. La verdad que te lo agradezco enormemente!!
    Muy muy buen tutorial.
    Si la agregas sobre como configurar mit krb5 sería un excelente tutorial de SSO en linux.

    Saludos!.

  3. Muy buena la web, la explicacion esta muy bien descrita.
    Hay un error… para que funcione correctamente el cpu, en el cpu.conf hay que añadir:
    USER_OBJECT_CLASS = account,posixAccount,shadowAccount,top,hostObject,authorizedServiceObject
    Esos dos ultimos objetos, pq si no falla el authorizedService, que he tenido que verlo con un strace pq no salian los errores por ningun sitio jeje.

  4. Hola, he segudio éste documento para practicar con LDAP y me ha parecido muy bueno, el problema es que me encuentro atascado en la parte de cargar la información en ldap, concretamente me da el siguiente error:

    /etc/ldap/ldif# slapadd -l policy.ldif
    str2entry: invalid value for attributeType objectClass #2 (syntax 1.3.6.1.4.1.1466.115.121.1.38)
    slapadd: could not parse entry (line=1)
    _#################### 100.00% eta none elapsed none fast!

    root@antonio-desktop:/etc/ldap/ldif# slapadd -l users.ldif
    str2entry: invalid value for attributeType objectClass #4 (syntax 1.3.6.1.4.1.1466.115.121.1.38)
    slapadd: could not parse entry (line=1)
    _#################### 100.00% eta none elapsed none fast!
    Closing DB…

    Le he dado muchas vueltas pero no encuentro dónde está el problema. ¿ Se te ocuerre dónde puede estar el error ?

    Muchas gracias,

    enhorabuena por los documentos puestos en la página.

  5. Hola de nuevo, he conseguido cargar datos al ldap, el problema con el que me enfrento ahora es que no me puedo conectar en modo seguro. Haciendo a mano me da el siguiente error:

    openssl s_client -connect localhost:636 -showcerts -state -CAfile /etc/ldap/ssl/host-server_crt.pem

    139667950499488:error:0200100D:system library:fopen:Permission denied:bss_file.c:169:fopen(‘/etc/ldap/ssl/host-server_crt.pem’,’r’)
    139667950499488:error:2006D002:BIO routines:BIO_new_file:system lib:bss_file.c:174:
    139667950499488:error:0B084002:x509 certificate routines:X509_load_cert_crl_file:system lib:by_file.c:274:
    CONNECTED(00000003)
    SSL_connect:before/connect initialization
    SSL_connect:SSLv3 write client hello A
    SSL_connect:failed in SSLv3 read server hello A
    139667950499488:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:591:

    no peer certificate available

    No client certificate CA names sent

    SSL handshake has read 0 bytes and written 0 bytes

    New, (NONE), Cipher is (NONE)
    Secure Renegotiation IS NOT supported
    Compression: NONE
    Expansion: NONE
    SSL-Session:
    Protocol : SSLv3
    Cipher : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Key-Arg : None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1331591171
    Timeout : 7200 (sec)
    Verify return code: 0 (ok)

    Si lo intento con phpldapadmin también me lo dice

    Could not start TLS. (antonio-desktop LDAP Server)
    Error: No se ha podido iniciar TLS. Por favor, revise su configuración LDAP

  6. Una duda sobre el servidor LDAP es posible tener dos admins uno digamos es el master tiene acceso a todo el directorio ldap y otro digamos delegado que solo pueda acceder a gestionar una sola OU y no pueda salir de ese nivel.

    Saludos.

Leave a Reply

Your email address will not be published. Required fields are marked *