Stressting LDAP for fun and profit

Hace casi dos años, el grupo de Software Libre de PDVSA, la petrolera estatal venezolana, estaba indeciso sobre la utilización de OpenLDAP como solución de directorio compatible con LDAPv3 que les permitiera sustituir parcial o totalmente Microsoft Active Directory. Ellos/as me plantearon su mayor duda: ¿qué tan robusto es OpenLDAP en entornos empresariales?

Yo ya había utilizado OpenLDAP extensivamente en entornos empresariales, incluso en convivencia con Microsoft Active Directory cuando trabajé en el Centro de Cómputo de EDELCA, la principal empresa eléctrica en Venezuela. Ese setup ahora está utilizando 389 (antes Fedora Directory Server) para sincronizar claves con MSAD para más de 17 mil personas y centenares de miles de objetos, y a pesar de que lo había visto funcionar bajo cargas muy inusuales, y usábamos clusters con distribución geográfica, yo también tenía la duda de los números.

Decidí probar esto en varios frentes. El primero, generar LDIFs muy grandes, y averiguar cuánto representarían en disco una vez almacenados en Berkeley DB, suponiendo que se fuera a utilizar ese popular backend para OpenLDAP. Esto nos daría una idea, exceptuando índices y optimizaciones, de los requerimientos en memoria de un producto como OpenLDAP en escenarios muy grandes. El segundo frente estaba limitado a las pruebas de tiempo de respuesta de la aplicación ante escenarios de búsqueda.

La razón por la que pruebo búsquedas es porque esta es la aplicación más común de un directorio LDAP. Mis pruebas fueron locales usando TCP/IP porque, aparte de contaminar los tiempos, en mi experiencia los tiempos de respuesta de un directorio LDAP, cuando está networked, se diluyen en los tiempos de procesamiento de la aplicación; en EDELCA preparé un informe que estudiaba la razón de un problema con GNOME Evolution en el cual el autocompletado tardaba varios segundos en responder, aunque en trazas de red se evidenciaba que el directorio LDAP había respondido en milisegundos (un bug en la aplicación) Imagine otro escenario: en la firma y la comprobación de firma de una infraestructura de llave pública dependiente de LDAP, ¿será el tiempo de respuesta de LDAP determinante cuando estamos potencialmente sobrecargando al cliente calculando hashes para la firma de un documento?

Por esto, el tiempo de respuesta local vs. networked no sería un indicativo de la experiencia del usuario final con el directorio, en parte el motivo de la preocupación del cliente. Además hay proxys en LDAP (montamos uno en PDVSA CRP, junto con Penrose como integrador de directorios, o como lo llaman en el mercado, un virtual directory) y estrategias de infraestructura para atacar esto de manera estructural.

Como no quería pasar seis meses de mi vida diseñando experimentos y ya que supuse que nadie iba a estar demasiado interesado en los resultados, dejé de lado algunos escenarios de borde y preparé un par de scripts, en Perl como es natural, para escribir LDIFs muy rápido y para consultar LDAPs muy rápido. Utilicé threads en Perl para su implementación (quería consultar en paralelo), aunque estoy prácticamente convencido por Luis Muñoz, Alejandro Imass y Ernesto Hernández-Novich de no repetir el uso de threads jamás.

De allí nació un pedestrian LDAP tester, que luego extendí a un stresster de MTAs a través de SMTP y a uno de procesos de autenticación en aplicaciones Web utilizando WWW::Mechanize. Esto es porque el proyecto con PDVSA involucraba las tres cosas: implementamos un cluster de Zimbra Collaboration Suite Community y 389 (FDS) utilizando Debian GNU/Linux en ocho servidores con Xen y NFSv3 para un poco más de 25 mil cuentas, a la fecha 18 meses en producción. Voy a subir los otros scripts pronto, pero vamos con los findings de LDAP, de mi nota informal de Julio 2008:

Cargar 200 mil registros (un LDIF de 66 MB.) en la base de datos tiene una representación en BDB de unos 928 MB. Asumamos que requeriríamos, worst case scenario ese espacio en memoria, sin índices ni los registros de replicación. Cargar esos 200 mil registros en OpenLDAP (slapadd) se toma entre 135 y 180 minutos aproximadamente. Afortunadamente la carga se hace sólo una vez. Indizar la base de datos (slapindex) es relativamente rápido y el tiempo se mantiene igual con 1 mil o 200 mil registros. Esto es lógico y bueno.

Hice una búsqueda similar a una consulta de una libreta de direcciones popular. Esa búsqueda generó 15 mil resultados (el número de resultados se puede restringir en OpenLDAP, pero a efectos de la prueba lo desactivé) — en mis pruebas, 100 clientes consultando paralelamente por TCP/IP y obteniendo los 15 mil resultados, sin limitar los atributos más allá de las ACLs se tomó:

real    0m12.048s
user    0m10.413s
sys     0m0.768s

Lo cual es más que aceptable. Ahora bien, el uso en memoria RAM del servicio está en los 640 MB. de memoria virtual y casi 200 MB. en memoria residente. Esto representa el 15% de la memoria del servidor que utilicé, un HP Integrity descontinuado para 2008, que tiene 2 GB. de RAM. Al introducir índices, estos se cargan en memoria, así como el número de objetos que se quiere tener en caché (yo coloqué 50 mil) por lo que habría que pensar en al menos unos 4 GB. de RAM para un volumen así.

Creando índices para los atributos cn, sn, givenName y mail, y ajustando un poco los parámetros de configuración de BerkeleyDB (tabulado del FAQ-O-Matic) la indización se toma un tiempo bastante mayor, para un tiempo de respuesta con la misma búsqueda no mucho menor:

real    0m10.540s
user    0m8.593s
sys     0m0.480s

Por lo que, en general, optimizar poco los índices tendría mucho impacto en las cargas y poco impacto en las búsquedas: si vas a indizar, indiza todo lo que quieras y puedas. Lo otro crítico de optimizar es el DBCONFIG; para 2008, y gracias en parte al oscurantismo de Oracle, era más arte que ciencia. El FAQ-O-Matic de OpenLDAP, como siempre, da la mejor información, ver los artículos 1072, 1075, 42, 738 y 893.

Now for the goodies, hay tres scripts que publiqué en mi carpeta de CPAN. El primero genera LDIFs, de forma muy básica, entradas tipo libreta de direcciones (plt-ldif-generator); el segundo hace las consultas con threads (plt-bambam) y el tercero es una muestra del patrón de uso del tester (plt-usage-pattern), en shell script.

Necesita un Perl razonablemente moderno, compilado con soporte para threads, la librería de Threads de Perl y el módulo Net::LDAP. Un aptitude install libthreads-perl libnet-ldap-perl es suficiente. El primer script también espera que usted provea un root.ldif (el nombre se puede cambiar en el código) con la entrada base del directorio (dc=foo,dc=bar; por ejemplo) y un model.ldif que tenga la entrada modelo con tags que el script sustituirá (obvio, puede usar un LDIF prehecho), algo como:

dn: cn=#CN#,dc=foo,dc=bar
objectClass: top
objectClass: inetorgperson
uid: #USER#
cn: #CN#
sn: #SN#
givenName: #FN#

Importante: como ya dije antes, algunos casos de uso están fuera de la prueba que realicé, en la mayoría de los casos porque no impactan la prueba sensiblemente (por ejemplo, autenticar las conexiones usando credenciales, cifrado con TLS/SSL y otros, no sería un problema grave) aunque sí podría nombrar algunos elementos que influyen en el performance cuando se lleva OpenLDAP a la práctica en escenarios distintos al trivial, como por ejemplo el uso de muchos schemas y de atributos divergentes en las entradas del directorio, sobrecargar el servicio con temas administrativos como largas ACLs dinámicas y sobre todo altos niveles de verbosidad y overlays de terceros. Afortunadamente, ninguno de estos es un must para un setup grande de LDAP. El código de mis scripts también debe tener bugs y está terriblemente documentado, pero le cumple el propósito perfectamente.

Estampillado de tiempo según RFC 3161 con software libre

Desde hace un año he estado trabajando con OpenTSA, un conjunto de parches a OpenSSL para incorporar soporte al estampillado de tiempo en la popular suite de criptografía de llave pública.

Muy a grandes rasgos, el estampillado de tiempo llena una necesidad corporativa e individual de certificar que una data existía a partir de cierto momento en el tiempo. Adicionalmente, al estar involucrado en una infraestructura de llave pública, nos trae las ventajas de la autenticidad y la integridad. Lamentablemente, hoy en día organizaciones en todo el Mundo hacen inversiones de varios cientos de miles de dólares en tecnología para estampillado de tiempo.

Hace un año OpenTSA tenía muchos problemas, ya que se necesitaba bastante OpenSSL-fu para parchar la popular y sobre todo ubicua suite de criptografía. En distribuciones mainstream como Debian tenemos gran cantidad de software que se enlaza con OpenSSL y con muchas otras cosas (p.ej., Apache con MySQL y Postgres… out of the box) y eso dificulta mucho la accesibilidad de las tecnologías que van saliendo.

En Noviembre 2009, OpenSSL publicó la beta4 de OpenSSL 1.0.0, que absorbe el valioso trabajo de Zoltan Glozik y sus colaboradores. Junto a mod_tsa del proyecto OpenTSA original, hoy en día es más que feasible poner a operar un servicio de estampillado de tiempo bajo HTTP(S) POST que le permita a un/a usuario/a relativamente lego en cuanto a criptografía poder hacer estampillados de su data.

A la fecha, no existe en Debian un paquete disponible de OpenSSL 1.0.0. Un paquete de este tipo no puede entrar en una distribución no autocontenida como experimental ya que hay demasiados paquetes compilados contra la versión 0.9.8. Así que en principio hay dos formas de hacer esto: con chroots o máquinas virtuales o esperar que el paquete entre en unstable. Yo utilizo equivs para crear los paquetes de openssl, libssl0.9.8 y libssl0.9.8-dev y no romper el sistema de paquetes. Espere que paquetes como wget o curl que hacen uso extensivo desde su arranque de SSL no funcionen o tenga que recompilarlos.

No es necesario aplicar parches en OpenSSL. Ya OpenSSL 1.0.0-beta4 tiene soporte para timestamping. Lo único que se necesita compilar es mod_tsa para Apache, y como Apache y sus dependencias también dependen extensivamente de SSL, lo mejor es compilar directo httpd 2.2 desde los repositorios fuente. Finalmente se compila mod_tsa, que necesita una DB para guardar las estampillas: MySQL, PostgreSQL o Firebird. Sólo se necesita una tabla sin funciones complejas: MySQL es lo más NoSQL que es accesible colocar.

¿Cómo se usa? Cuando se tiene un archivo o una data, se aplica openssl ts para obtener el timestamping request y luego se manda a firmar el request en la autoridad de estampillado de tiempo. El emisor distribuye la estampilla junto con la data, y el/la interesado/a puede verificar la integridad y existencia de la data teniendo la data, la estampilla y el certificado de la autoridad de estampillado.

Una reflexión final: sigo abogando por el uso de GnuPG para garantizar autenticidad, integridad y cifrado en todas las comunicaciones vía electrónica. Sin embargo, GnuPG carece de mecanismos pulidos para estampillado de tiempo y aunque la mayor parte de nuestras comunicaciones las hacemos vía correo electrónico, ni siquiera allí podemos contar con las estampas de los servidores de correo intermedio. Por esto considero que enfocar el uso de OpenSSL sólo para TSA puede ser una muy buena idea, complementada con GnuPG.

Este blog refleja única y exclusivamente mis opiniones, y no las de mis empleadores, las de las organizaciones de las que formo parte ni las de ninguna otra persona natural o jurídica, pública o privada, nacional o extranjera.