Fortaleciendo nuestras contraseñas

Si una de las promesas que tiene para este cierre de año es fortalecer las contraseñas en sus equipos personales, cambiarlas mensualmente y no repetir la misma contraseña en al menos doce cambios. En este artículo se le explicará como hacerlo sin tener que invertir una uva en ello, todo esto gracias al paquete libpam-cracklib en Debian, el procedimiento mostrado debe aplicarse a otras distribuciones derivadas de Debian.

Pareciese lógico que algunas de las mejores prácticas para el fortalecimiento de las contraseñas son las siguientes:

  • Cambiar las contraseñas periódicamente.
  • Establecer una longitud mínima en las contraseñas.
  • Establecer buenas reglas para las nuevas contraseñas, es decir, mezcla entre letras mayúsculas, minúsculas, dígitos y caracteres alfanuméricos.
  • Mantener un histórico de las contraseñas usadas previamente, de ese modo, alentamos a los usuarios establecer nuevas contraseñas.
  • Indicarle a los usuarios que es inaudito que se anoten las contraseñas en un post-it y se dejen pegadas en los monitores o incluso en las gavetas de sus archivadores.

El primer paso es instalar el paquete libpam-cracklib

# apt-get install libpam-cracklib

A partir de la versión 1.0.1-6 de PAM se recomienda manejar la configuración vía pam-auth-update. Por lo tanto, por favor tome un momento y lea la sección 8 del manual del comando pam-auth-update para aclarar su uso y ventajas.

$ man 8 pam-auth-update

Ahora establezca una configuración similar a la siguiente, vamos primero con la exigencia en la fortaleza de las contraseñas, para ello edite o cree el fichero /usr/share/pam-configs/cracklib.


Name: Cracklib password strength checking
Default: yes
Priority: 1024
Conflicts: unix-zany
Password-Type: Primary
Password:
	requisite			pam_cracklib.so retry=3 minlen=8 difok=3
Password-Initial:
	requisite			pam_cracklib.so retry=3 minlen=8 difok=3

NOTA: Le recomiendo leer la sección 8 del manual de pam_cracklib para encontrar un mayor numero de opciones de configuración. Esto es solo un ejemplo.

En versiones previas el modulo pam_cracklib hacia uso del fichero /etc/security/opasswd para conocer si la propuesta de cambio de contraseña no había sido utilizada previamente. Dicha funcionalidad ahora corresponde al nuevo modulo pam_pwhistory

Definamos el funcionamiento de pam_pwhistory a través del fichero /usr/share/pam-configs/history.


Name: PAM module to remember last passwords
Default: yes
Priority: 1023
Password-Type: Primary
Password:
	requisite			pam_pwhistory.so use_authtok enforce_for_root remember=12 retry=3
Password-Initial:
	requisite			pam_pwhistory.so use_authtok enforce_for_root remember=12 retry=3

NOTA: Para mayor detalle de las opciones puede revisar la sección 8 del manual de pam_pwhistory

Seguidamente proceda a actualizar la configuración de PAM vía pam-auth-update.

Una vez cubierta la fortaleza de las contraseñas nuevas y de evitar la reutilización de las ultimas 12, de acuerdo al ejemplo mostrado, resta cubrir la definición de los periodos de cambio de las contraseñas.

Para futuros usuarios debemos ajustar ciertos valores en el fichero /etc/login.defs


#
# Password aging controls:
#
#       PASS_MAX_DAYS   Maximum number of days a password may be used.
#       PASS_MIN_DAYS   Minimum number of days allowed between password changes.
#       PASS_WARN_AGE   Number of days warning given before a password expires.
#
PASS_MAX_DAYS   30
PASS_MIN_DAYS   0
PASS_WARN_AGE   5

Las reglas previas no aplicaran para los usuarios existentes, pero para este tipo de usuarios podremos hacer uso del comando chage de la siguiente manera:

# chage -m 0 -M 30 -W 5 ${user}

Donde el valor de ${user} debe ser reemplazo por el username.

apt-get detrás de proxy con autenticación NTLM

Por motivos que no vienen al caso discutir en este artículo tuve que instalar Debian GNU/Linux detrás de un proxy que aún utiliza NTLM como medio de autenticación, aunque NTLM ya no es recomendado por Microsoft desde hace años en pro de usar Kerberos.

Una vez instalada la distribución quería utilizar apt-get para actualizarla e instalar nuevos paquetes, el resultado fue que apt-get no funciona de manera transparente detrás de un proxy con autenticación NTLM. La solución fue colocar un proxy interno que esté atento a peticiones en un puerto particular en el host, el proxy interno se encargará de proveer de manera correcta las credenciales al proxy externo.

La solución descrita previamente resulta sencilla al utilizar cntlm. En principio será necesario instalarlo vía dpkg, posteriormente deberá editar los campos apropiados en el fichero /etc/cntlm.conf

  • Username
  • Domain
  • Password
  • Proxy

Seguidamente reinicie el servicio:

# /etc/init.d/cntlm restart

Ahora solo resta configurar apt-get para que utilice nuestro proxy interno, para ello edite el fichero /etc/apt.conf.d/02proxy

Acquire::http::Proxy "http://127.0.0.1:3128";

NOTA: Se asume que el puerto de escucha de cntlm es el 3128.

Ahora puede hacer uso correcto de apt-get:

# apt-get update
# apt-get upgrade
...

NOTA FINAL: Es evidente que cualquier comando o herramienta que necesite autenticarse contra el proxy externo deberá configurarlo para que utilice el proxy interno, lo explicado en este artículo no solo aplica para el comando apt-get.

Enviando correos con Perl

Regularmente los administradores de sistemas requieren notificar, vía correo electrónico, a sus usuarios de ciertos cambios o nuevos servicios disponibles. La experiencia me ha indicado que el usuario aprecia más un correo personalizado que uno general. Sin embargo, lograr lo primero de manera manual es bastante tedioso e ineficaz. Por lo tanto, es lógico pensar en la posibilidad de automatizar el proceso de envío de correos electrónicos personalizados, en este artículo, explicaré una de las tantas maneras de lograrlo haciendo uso del lenguaje de programación Perl.

En CPAN podrá encontrar muchas alternativas, recuerde el principio TIMTOWTDI. Sin embargo, la opción que más me atrajo fue MIME::Lite:TT, básicamente este módulo en Perl es un wrapper de MIME::Lite que le permite el uso de plantillas, vía Template::Toolkit, para el cuerpo del mensaje del correo electrónico. También puede encontrar MIME::Lite::TT::HTML que le permitirá enviar correos tanto en texto sin formato (MIME::Lite::TT) como en formato HTML. Sin embargo, estoy en contra de enviar correos en formato HTML, lo dejo a su criterio.

Una de las ventajas de utilizar Template::Toolkit para el cuerpo del mensaje es separar en capas nuestra script, si se observa desde una versión muy simplificada del patrón MVC, el control de la lógica de programación reside en el script en Perl, la plantilla basada en Template Toolkit ofrecería la vista de los datos, de modo tal que podríamos garantizar que la presentación está separada de los datos, los cuales pueden encontrarse desde una base de datos o un simple fichero CSV. Otra ventaja evidente es el posible reuso de componentes posteriormente.

Un primer ejemplo del uso de MIME::Lite:TT puede ser el siguiente:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

my %params;
$params{first_name} = "Milton";
$params{last_name}  = "Mazzarri";
$params{username}   = "milmazz";
$params{groups}     = "sysadmin";

my $msg = MIME::Lite::TT->new(
    From        => 'jdoe@example.com',
    To          => 'milmazz@example.com',
    Charset     => 'utf8',
    TimeZone    => 'America/Caracas',
    Subject     => 'Example',
    Template    => 'example.txt.tt',
    TmplOptions => \%options,
    TmplParams  => \%params,
);

$msg->send();

Y el cuerpo del correo electrónico, lo que en realidad es una plantilla basada en Template::Toolkit, vendría definido en el fichero example.txt.tt de la siguiente manera:

Hola [% last_name %], [% first_name %].

Tu nombre de usuario es [% username %].

Un saludo, feliz día.

Su querido BOFH de siempre.

En el script en Perl mostrado previamente podemos percatarnos que los datos del destinario se encuentran inmersos en la lógica. Por lo tanto, el siguiente paso sería desacoplar esta parte de la siguiente manera:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Class::CSV;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => 'jdoe@example.com',
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    $msg->send();
}

Ahora los datos de los destinarios los extraemos de un fichero en formato CSV, en este ejemplo, el fichero en formato CSV lo hemos denominado example.csv.

Cabe aclarar que $msg->send() realiza el envío por medio de Net::SMTP y podrá usar las opciones que se describen en dicho módulo. Sin embargo, si necesita establecer una conexión SSL con el servidor SMTP es oportuno recurrir a Net::SMTP::SSL:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Net::SMTP::SSL;
use Class::CSV;

my $from = 'jdoe@example.com';
my $host = 'mail.example.com';
my $user = 'jdoe';
my $pass = 'example';

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => $from,
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    my $smtp = Net::SMTP::SSL->new( $host, Port => 465 )
      or die "No pude conectarme";
    $smtp->auth( $user, $pass )
      or die "No pude autenticarme:" . $smtp->message();
    $smtp->mail($from)                 or die "Error:" . $smtp->message();
    $smtp->to( $line->email() )        or die "Error:" . $smtp->message();
    $smtp->data()                      or die "Error:" . $smtp->message();
    $smtp->datasend( $msg->as_string ) or die "Error:" . $smtp->message();
    $smtp->dataend()                   or die "Error:" . $smtp->message();
    $smtp->quit()                      or die "Error:" . $smtp->message();
}

Note en este último ejemplo que la representación en cadena de caracteres del cuerpo del correo electrónico viene dado por $msg->as_string.

Para finalizar, es importante mencionar que también podrá adjuntar ficheros de cualquier tipo a sus correos electrónicos, solo debe prestar especial atención en el tipo MIME de los ficheros que adjunta, es decir, si enviará un fichero adjunto PDF debe utilizar el tipo application/pdf, si envía una imagen en el formato GIF, debe usar el tipo image/gif. El método a seguir para adjuntar uno o más ficheros lo dejo para su investigación ;)

Cambiar focos delanteros de un Ford Fiesta

Advertencia: Esta es la primera de una posible serie de entradas acerca de tópicos relacionados con la mecánica y el mantenimiento regular de automóviles, desde ya aclaro que no es mi especialidad por lo cual agradezco cualquier comentario, sugerencia o crítica. Sin embargo, espero que por medio de esta serie de artículos logre transmitir lo poco que voy aprendiendo acerca de este fascinante tema.

Aprovechando que uno de los bombillos convencionales de mi automóvil llegó al final de su vida útil decidí mejorar la iluminación de los faros delanteros debido los constantes viajes que realizo de noche y las malas condiciones (lluvia, neblina) con las que me encuentro ocasionalmente.

Antes de realizar la compra, tenía claro lo siguiente: No iba a modificar el automóvil y el objetivo es mejorar la visibilidad al conducir en condiciones adversas, nada de HID y tuning innecesario.

Debido a la poca variedad que logré encontrar acá me decanté por unos bombillos OSRAM Cool Blue estándar (4000 Kelvin), no encontré ni los Cool Blue Mega (4300K), Cool Blue Hyper (5000K) o los Night Breaker de OSRAM. Por cierto, el modelo de las lámparas para mi automóvil son H7 (bajas) y H1 (altas). Revise los detalles técnicos de su vehículo en el Manual del propietario.

El procedimiento para el cambio de los focos es sencillo, está documentado en el Manual del Propietario y Mantenimiento del vehículo. Sin embargo, he publicado un álbum de fotos con ciertas notas y descripciones para mejor comprensión del tema en mi cuenta Flickr.

Después de instalar las lámparas puedo decir que estoy contento con las Cool Blue estándar de OSRAM, se percibe de inmediato una notable mejoría respecto a la lámpara convencional, consumiendo la misma potencia en vatios. Las Cool Blue emiten luz blanca clara y azulada, el haz de luz es similar a las lámparas HID (las cuales por lo que leí van del rango 4000K a 12000K), lo más importante es que no tuve que modificar mi carro en lo absoluto para obtener dicho efecto.

Construyendo de manera efectiva y rápida imágenes ISO de Debian con jigdo

Si usted desea el conjunto de CD o DVD para instalar Debian, tiene muchas posibilidades, desde la compra de los mismos, muchos de los vendedores contribuyen con Debian. También puede realizar descargas vía HTTP/FTP, vía torrent o rsync. Pero en este artículo se discutirá sobre un método para construir las imágenes ISO de Debian de manera eficiente, sobretodo si cuenta con un repositorio local de paquetes, dicho método se conoce de manera abreviada como jigdo o Jigsaw Download.

Las ventajas que ofrece jigdo están bien claras en el portal de Debian, cito:

¿Por qué jigdo es mejor que una descarga directa?

¡Porque es más rápido! Por varias razones, hay muchas menos réplicas para imágenes de CDs que para el archivo «normal» de Debian. Consecuentemente, si descarga desde una réplica de imágenes de CD, esa réplica no sólo estará más lejos de su ubicación, además estará sobrecargada, especialmente justo después de una publicación.

Además, algunos tipos de imágenes no están disponibles para descarga completa como .iso porque no hay suficiente espacio en nuestros servidores para alojarlas.

Considero que la pregunta pertinente ahora es: ¿Cómo descargo la imagen con jigdo?.

En primer lugar, instalamos el paquete jigdo-file.

# aptitude install jigdo-file

Mi objetivo era generar los 2 primeros CD para Debian Lenny, para la fecha de publicación de este artículo la versión más reciente es la 5.0.7. La lista de imágenes oficiales para jigdo las puede encontrar acá.


milmazz@manaslu /tmp $ cat files

http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.jigdo

http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.template

http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-2.jigdo

http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-2.template

milmazz@manaslu /tmp $ wget -c -i files
--2010-12-02 12:39:52--  http://cdimage.debian.org/debian-cd/5.0.7/i386/jigdo-cd/debian-507-i386-CD-1.jigdo
Resolving cdimage.debian.org... 130.239.18.163, 130.239.18.173, 2001:6b0:e:2018::173, ...
Connecting to cdimage.debian.org|130.239.18.163|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 31737 (31K) 1
Saving to: `debian-507-i386-CD-1.jigdo'

100%[===================================================================================================================>] 31.737      44,7K/s   in 0,7s

...

FINISHED --2010-12-02 12:50:15--
Downloaded: 4 files, 27M in 10m 21s (44,7 KB/s)
milmazz@manaslu /tmp $ ls
debian-507-i386-CD-1.jigdo  debian-507-i386-CD-1.template  debian-507-i386-CD-2.jigdo  debian-507-i386-CD-2.template files

Una vez descargados los ficheros necesarios, es hora de ejecutar el comando jigdo-lite, siga las instrucciones del asistente.


milmazz@manaslu ~ $ jigdo-lite debian-507-i386-CD-2.jigdo

Jigsaw Download "lite"
Copyright (C) 2001-2005  |  jigdo@
Richard Atterer          |  atterer.net
Loading settings from `/home/milmazz/.jigdo-lite'

-----------------------------------------------------------------
Images offered by `debian-507-i386-CD-2.jigdo':
1: 'Debian GNU/Linux 5.0.7 "Lenny" - Official i386 CD Binary-2 20101127-16:55 (20101127)' (debian-507-i386-CD-2.iso)

Further information about `debian-507-i386-CD-2.iso':
Generated on Sat, 27 Nov 2010 17:02:14 +0000

-----------------------------------------------------------------
If you already have a previous version of the CD you are
downloading, jigdo can re-use files on the old CD that are also
present in the new image, and you do not need to download them
again. Mount the old CD ROM and enter the path it is mounted under
(e.g. `/mnt/cdrom').
Alternatively, just press enter if you want to start downloading
the remaining files.
Files to scan:

El comando despliega información acerca de la imagen ISO que generará, en este caso particular, debian-507-i386-CD-2.iso. Además, jigdo-lite puede reutilizar ficheros que se encuentren en CD viejos y así no tener que descargarlos de nuevo. Sin embargo, este no era mi caso así que presione la tecla ENTER.


-----------------------------------------------------------------
The jigdo file refers to files stored on Debian mirrors. Please
choose a Debian mirror as follows: Either enter a complete URL
pointing to a mirror (in the form
`ftp://ftp.debian.org/debian/'), or enter any regular expression
for searching through the list of mirrors: Try a two-letter
country code such as `de', or a country name like `United
States', or a server name like `sunsite'.
Debian mirror [http://debian.example.com/debian/]:

En esta fase jigdo-lite solicita la dirección URL completa de un repositorio, aproveche la oportunidad de utilizar su repositorio local si es que cuenta con uno. Luego de presionar la tecla ENTER es tiempo de relajarse y esperar que jigdo descargue todos y cada uno de los ficheros que componen la imagen ISO.

Luego de descargar los paquetes y realizar las operaciones necesarias para la construcción de la imagen ISO jigdo le informará los resultados.


FINISHED --2010-12-01 14:43:50--
Downloaded: 6 files, 2,5M in 1,8s (1,39 MB/s)
Found 6 of the 6 files required by the template
Successfully created `debian-507-i386-CD-2.iso'

-----------------------------------------------------------------
Finished!
The fact that you got this far is a strong indication that `debian-507-i386-CD-2.iso'
was generated correctly. I will perform an additional, final check,
which you can interrupt safely with Ctrl-C if you do not want to wait.

OK: Checksums match, image is good!

Ahora bien, haciendo uso de un repositorio local, es bueno preguntarse en cuanto tiempo aproximadamente puedes construir tu imagen ISO, en mi caso el tiempo de construcción de debian-507-i386-CD-2.iso fue de:


milmazz@manaslu ~ $ time jigdo-lite debian-507-i386-CD-2.jigdo

...

real	8m35.704s
user	0m13.101s
sys	0m16.569s

Nada mal, ¿no les parece?.

Ahora bien, haciendo uso de un repositorio local, es bueno preguntarse en cuanto tiempo aproximadamente puedes construir tu imagen ISO, en mi caso el tiempo de construcción de debian-507-i386-CD-2.iso fue de:

Referencias

Introducción a pruebas de rendimiento sobre aplicaciones Web con Multi-Mechanize (Parte I)

Corey Goldberg, autor de la herramienta para pruebas de rendimiento Pylot, anunciaba a mediados del mes de febrero de este año que estaba trabajando en un nuevo framework para este mismo tipo de pruebas, al que denominó Multi-Mechanize. Pero parece lógico preguntarse: ¿por qué otra herramienta?, Corey argumentaba que el diseño de Pylot está bastante limitado debido al hecho que los casos de pruebas deben establecerse en forma declarativa, vía XML, llegando a la conclusión que era necesario un lenguaje de scripting para la definición de los casos de pruebas, además, la mejora en la concurrencia de las mismas.

Multi-Mechanize es un framework orientado a pruebas de rendimiento y carga en sitios Web. Este framework permite la ejecución simultánea de scripts en Python por medio de un motor multi-proceso, multi-hilo para reproducir secuencias de comandos y generar usuarios virtuales de manera concurrente, todo esto con el fin de establecer carga en contra de un sitio o servicio Web.

Una de las grandes ventajas de Multi-Mechanize es que ofrece la posibilidad de incluir en los scripts el poderoso módulo mechanize (derivado de WWW::Mechanize en Perl) junto con el lenguaje de programación Python. Permitiendo crear scripts de pruebas que simulan la actividad de usuarios virtuales de manera rápida y sencilla, estos scripts generarán peticiones HTTP para navegar o enviar solicitudes de forma inteligente un sitio o servicio Web.

Los resultados se almacenan en formato CSV o en una base de datos, junto con el informe en formato HTML que contiene las estadísticas y gráficos.

Un ejemplo de la capacidad de reportes que ofrece Multi-Mechanize puede verla en http://code.google.com/p/multi-mechanize/

Requisitos

Multi-Mechanize requiere Python 2.x (2.6 o 2.7), si desea generar gráficos a partir de las pruebas, debe instalar Matplotib y sus dependencias, mayor información vea la página de Preguntas de Uso Frecuente

De manera adicional necesitará el módulo mechanize.

La instalación de los requisitos en Debian GNU/Linux y derivados es tal como se describe a continuación:

# aptitude install python-mechanize python-matplotlib

Almacenamiento de datos de prueba y resultados en Bases de Datos

Opcionalmente, los resultados pueden ser almacenados en una base de datos. Para ello debe agregar la opción results_database en el fichero de configuración config.cfg, el cual define la cadena de conexión a la base de datos.

El almacenamiento en base de datos requiere tener instalado previamente SQLAlchemy

Algunos ejemplos de conexión a bases de datos son los siguientes:

SQLite sqlite:///dbname
MySQL mysql://user:password@localhost/dbname
PostgreSQL postgresql://user:password@host:port/dbname
MS SQL Server mssql://mydsn

Tenga en cuenta que el soporte de SQLite es nativo en Python. Por lo tanto, no es necesario su instalación y configuración.

Para mayor información sobre el soporte a base de datos vea Database Storage

Instalando Multi-Mechanize

Puede descargar el proyecto Multi-Mechanize desde http://code.google.com/p/multi-mechanize/downloads/list

Una vez descargado el proyecto Multi-Mechanize, descomprima y vaya a la raíz del proyecto y ejecute desde la línea de comandos:

$ python multi-mechanize.py default_project

Este comando ejecutará el proyecto de prueba incluido en la distribución de Multi-Mechanize, el proyecto de prueba generará datos aleatorios.

NOTA: Se le recomienda analizar los scripts incluidos en projects/default_project/test_scripts/

Comenzando con Multi-Mechanize

Para iniciar, usted puede usar el directorio default_project que incluye la distribución de Multi-Mechanize. Si usted necesita manejar múltiples proyectos, solo cree un directorio al mismo nivel de default_project para cada uno de los proyectos, se le recomienda mantener la misma estructura del proyecto por omisión. Recuerde que se necesita especificar el proyecto que ejecutará desde la línea de comandos.

Cada proyecto debe contener lo siguiente:

config.cfg
Fichero de configuración, establezca sus opciones de prueba.
test_scripts
Directorio de almacenamiento de scripts de sus usuarios virtuales.
results
Directorio para el almacenamiento de los resultados. Acá encontrará subdirectorios cuyos nombres son una estampa de tiempo y son creados por cada ejecución de las pruebas y contiene datos en formato CSV, un sumario en formato HTML e imágenes en formato PNG.

Formato del fichero de configuración

Cada proyecto contiene un fichero config.cfg donde debe definir la configuración para una prueba.

A continuación se le muestra un fichero de configuración que muestra todas las opciones posibles de configuración:

[global]
run_time: 300
rampup: 300
console_logging: off
results_ts_interval: 30
results_database: sqlite:///projects/default_project/results.db
post_run_script: python projects/default_project/foo.py

[user_group-1]
threads: 30
script: example_mock.py

[user_group-2]
threads: 30
script: example_mock.py

Opciones globales

run_time
Duración de la prueba en segundos
rampup
Duración en segundos de la rampa de usuarios (hilos)
console_logging
Activar/Desactivar el registro a la salida estándar
results_ts_interval
Intervalos de las series de tiempo para el análisis de los resultados expresadas en segundos.
results_database
Cadena de conexión a base de datos (opcional)
post_run_script
script que será invocado después de la culminación de la prueba (opcional)

Grupos de usuarios

threads
Número de hilos/usuarios virtuales
script
Script del usuario virtual a ejecutar.

Para mayor información sobre el formato del fichero de configuración consulte: Config File.

En la segunda parte de este artículo mostraré algunos ejemplos con casos de pruebas que se ejecutarán sobre una instalación de Trac.

Generar reporte en formato CSV de tickets en Trac desde Perl

El día de hoy recibí una llamada telefónica de un compañero de labores en donde me solicitaba con cierta preocupación un “pequeño” reporte del estado de un listado de tickets que recién me había enviado vía correo electrónico puesto que no contaba con conexión a la intranet, al analizar un par de tickets me dije que no iba a ser fácil realizar la consulta desde el asistente que brinda el mismo Trac. Así que inmediatamente puse las manos sobre un pequeño script en Perl que hiciera el trabajo sucio por mí.

Es de hacer notar que total de tickets a revisar era el siguiente:

$ wc -l tickets
126 tickets

Tomando en cuenta el resultado previo, era inaceptable hacer dicha labor de manera manual. Por lo tanto, confirmaba que realizar un script era la vía correcta y a la final iba a ser más divertido.

Tomando en cuenta que el formato de entrada era el siguiente:

#3460
#3493

El formato de la salida que esperaba era similar a la siguiente:

3460,"No expira la sesión…",closed,user

Básicamente el formato implica el id, sumario, estado y responsable asociado al ticket.

Net::Trac le ofrece una manera sencilla de interactuar con una instancia remota de Trac, desde el manejo de credenciales, consultas, revisión de tickets, entre otros. A la vez, se hace uso del módulo Class::CSV el cual le ofrece análisis y escritura de documentos en formato CSV.

#!/usr/bin/perl

use warnings;
use strict;

use Net::Trac;
use Class::CSV;

# Estableciendo la conexion a la instancia remota de Trac
my $trac = Net::Trac::Connection->new(
    url => 'http://trac.example.com/project',
    user => 'user',
    password => 'password'
);

# Construccion del objecto CSV y definicion de opciones
my $csv = Class::CSV->new(
    fields => [qw/ticket sumario estado responsable/],
    line_separator => "\r\n",
    csv_xs_options => { binary => 1, } # Manejo de caracteres non-ASCII
);

# Nos aseguramos que el inicio de sesion haya sido exitoso
if ( $trac->ensure_logged_in ) {
    my $ticket = Net::Trac::Ticket->new( connection => $trac );

    # Consultamos cada uno de los tickets indicados en el fichero de entrada
    while ( my $line = <> ) {
        chomp($line);
        if ( $line =~ m/^#\d+$/ ) {
            $line =~ s/^#(\d+)$/$1/;
            $ticket->load($line);

            $csv->add_line(
                {
                    ticket => $ticket->id,
                    sumario => $ticket->summary,
                    estado => $ticket->status,
                    responsable => $ticket->owner
                }
            );
        }
        else {
            print "[INFO] La linea no cumple el formato requerido: $line\n";
        }
    }
    $csv->print();
}
else {
    print "No se pudieron asegurar las credenciales";
}

view raw trac_query.pl This Gist brought to you by GitHub.

La manera de ejecutar el script es la siguiente:

$ perl trac_query.pl tickets

En donde trac_query.pl es el nombre del script y tickets es el fichero de entrada.

Debo aclarar que el script carece de comentarios, mea culpa. Además, el manejo de opciones vía linea de comandos es inexistente, si desea mejorarlo puede hacer uso de Getopt::Long.

Cualquier comentario, sugerencia o corrección es bienvenida.

Instalando dependencias no-libres de JAVA en ambientes pbuilder

El día de hoy asumí la construcción de unos paquetes internos compatibles con Debian 5.0 (a.k.a. Lenny) que anteriormente eran responsabilidad de ex-compañeros de labores. El paquete en cuestión posee una dependencia no-libre, sun-java6-jre. En este artículo se describirá como lograr adecuar su configuración de pbuilder para la correcta construcción del paquete.

Asumiendo que tiene un configuración similar a la siguiente:

$ cat /etc/pbuilderrc
MIRRORSITE=http://example.com/debian
DEBEMAIL="Maintainer Name <mail@example.com>"
DISTRIBUTION=lenny
DEBOOTSTRAP="cdebootstrap"
COMPONENTS="main contrib non-free"

Para mayor información sobre estas opciones sírvase leer:

$ man 5 pbuilderrc

Mientras intenta compilar su paquete en el ambiente proporcionado por pbuilder el proceso fallará ya que no se mostró la ventana para aceptar la licencia de JAVA. Podrá observar en el registro de la construcción del build un mensaje similar al siguiente:

Unpacking sun-java6-jre (from .../sun-java6-jre_6-20-0lenny1_all.deb) ...

sun-dlj-v1-1 license could not be presented
try 'dpkg-reconfigure debconf' to select a frontend other than noninteractive

dpkg: error processing /var/cache/apt/archives/sun-java6-jre_6-20-0lenny1_all.deb (--unpack):
subprocess pre-installation script returned error exit status 2

Para evitar esto altere la configuración del fichero pbuilderrc de la siguiente manera:

$ cat /etc/pbuilderrc
MIRRORSITE=http://example.com/debian
DEBEMAIL="Maintainer Name <mail@example.com>"
DISTRIBUTION=lenny
DEBOOTSTRAP="cdebootstrap"
COMPONENTS="main contrib non-free"
export DEBIAN_FRONTEND="readline"

Una vez alterada la configuración podrá interactuar con las opciones que le ofrece debconf.

Ahora bien, si usted constantemente tiene que construir paquetes con dependencias no-libres como las de JAVA, es probable que le interese lo que se menciona a continuación.

Si lee detenidamente la página del manual de pbuilder en su sección 8 podrá encontrar lo siguiente:

$ man 8 pbuilder
...
--save-after-login
--save-after-exec
Save the chroot image after exiting from the chroot instead of deleting changes.  Effective for login and execute session.
...

Por lo tanto, usaremos esta funcionalidad que ofrece pbuilder para insertar valores por omisión en la base de datos de debconf para que no se nos pregunte si deseamos aceptar la licencia de JAVA:

# pbuilder login --save-after-login
I: Building the build Environment
I: extracting base tarball [/var/cache/pbuilder/base.tgz]
I: creating local configuration
I: copying local configuration
I: mounting /proc filesystem
I: mounting /dev/pts filesystem
I: Mounting /var/cache/pbuilder/ccache
I: policy-rc.d already exists
I: Obtaining the cached apt archive contents
I: entering the shell
File extracted to: /var/cache/pbuilder/build//27657

pbuilder:/# cat > java-license << EOF
> sun-java6-bin shared/accepted-sun-dlj-v1-1 boolean true
> sun-java6-jdk shared/accepted-sun-dlj-v1-1 boolean true
> sun-java6-jre shared/accepted-sun-dlj-v1-1 boolean true
> EOF
pbuilder:/# debconf-set-selections < java-license
pbuilder:/# exit
logout
I: Copying back the cached apt archive contents
I: Saving the results, modifications to this session will persist
I: unmounting /var/cache/pbuilder/ccache filesystem
I: unmounting dev/pts filesystem
I: unmounting proc filesystem
I: creating base tarball [/var/cache/pbuilder/base.tgz]
I: cleaning the build env
I: removing directory /var/cache/pbuilder/build//27657 and its subdirectories

Presentaciones

Desde hace algunos meses he decidido recopilar y organizar algunas de las presentaciones que he dado hasta ahora en eventos de Software Libre, Universidades y empresas privadas.

El software que regularmente utilizo para realizar mis presentaciones es Beamer, una clase LaTeX que facilita enormente la producción de presentaciones de alta calidad, este software trabaja de la mano con pdflatex, también con dvips.

La lista de presentaciones que he recopilado hasta la fecha son las siguientes:

Análisis estático del código fuente en Python
Se describe el concepto del análisis estático del código, se indica los pasos a seguir para la detección de errores mediante la herramienta Pylint, se exponen sus funcionalidades, reportes y se muestran ejemplos para corregir los errores encontrados por la herramienta.
Desarrollo colectivo en Turpial
Se describe la visión del cliente para Twitter Turpial, sus funcionalidades actuales, el uso de herramientas como Transifex, PyBabel, Distutils, Sphinx, dichas herramientas facilitan y mejoran la calidad del software que se desarrolla.
Canaima GNU/Linux
Una introducción, se describe la historia, definición del proyecto Canaima, principales características, procesos para colaborar, enlaces de interés, entre otros.
Novela gráfica creada con el motor Ren’Py
Se relata la experiencia del desarrollo de una novela gráfica para niños de 5to. grado de educación, de acuerdo a currículo impartido en las escuelas venezolanas.
Trac
Herramientas libres para el apoyo en el proceso de desarrollo de software, se discute las características y funcionalidades que ofrece el software. Además del proceso de personalización por medio de complementos o plugins.
GnuPG, GNU Privacy Guard
Importancia del cifrado de la información, diferencias entre llaves simétricas y asimétricas, criptografía, fiestas de firmado de llaves, beneficios. Instalación y suo práctico de GnuPG.
Uso de dbconfig-common
Presentación que es parte de la serie mejores prácticas para el empaquetamiento de aplicaciones en Debian, se describe el uso de la herramienta y su respectiva integración con el asistente debhelper
Conociendo el framework web Django
Introducción, historia, características, primeros pasos, instalación y demostración de desarrollo de una aplicación sencilla bajo este excelente framework basado en el lenguaje de Programación Python

Las fuentes en LaTeX de las presentaciones, así como su licencia de uso y proceso de conversión al formato PDF se describe en el proyecto Presentations que he creado en github.

Agradezco enormemente cualquier comentario que pueda hacer respecto a los temas presentados puesto que en el próximo mes trataré de actualizar el contenido, así como incluir nuevas presentaciones. ¿Desearía poder conocer más sobre un tema en particular?, ¿cuál sería ese tema?.

Nota final: Si encuentra algún error por favor notificarlo vía issues del proyecto Presentations.

Mejoras en el comportamiento a la hora de eliminar un ForeignKey

Logo de Django Cuando un objeto referenciado por una clave foránea (ForeignKey) es eliminado, Django por omisión emula el comportamiento de la sentencia SQL ON DELETE CASCADE y también se elimina el objeto que contiene el ForeignKey.

A partir de la versión 1.3 de Django el comportamiento descrito en el párrafo anterior puede ser sobreescrito al especificar el argumento on_delete. Por ejemplo, si usted permite que una clave foránea pueda ser nula y usted desea que sea establecida a NULL cuando un objeto referenciado sea eliminado:

user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)

Los posibles valores para el argumento on_delete pueden encontrarse en django.db.models:

CASCADE
Eliminación en cascada, el comportamiento por omisión.
PROTECT
Prevee la eliminación del objeto referenciado al lanzar una excepción del tipo: django.db-IntegrityError.
SET_NULL
Establece la clave foránea a NULL, esto solo es posible si el argumento null es True.
SET_DEFAULT
Establece la clave foránea a su valor por omisión, tenga en cuenta que un valor por omisión debe ser establecido.
SET()
Establece el valor del ForeignKey indicado en SET(), si una función es invocada, el resultado de dicha función será el valor establecido.
DO_NOTHING
No tomar acciones. Si el gestor de base de datos requiere integridad referencial, esto causará una excepción del tipo IntegrityError.

A continuación un par de ejemplos de esta nueva funcionalidad:

# models.py
from django.db import models

class Author(models.Model):
    nickname = models.CharField(max_length=32)

    def __unicode__(self):
        return self.nickname

class Post(models.Model):
    author = models.ForeignKey(Author, blank=True, null=True, on_delete=models.SET_NULL)
    title = models.CharField(max_length=128)

    def __unicode__(self):
        return self.title

Nuestra sesión interactiva con el API sería similar a la siguiente:

$ python manage.py shell
>>> from ondelete.models import Author, Post

>>> Author.objects.all()
[]
# Creamos el autor
>>> author = Author(nickname='milmazz')
# Guardamos el objeto en la base de datos al usar de manera explícita el método save()
>>> author.save()

# Obtenemos el autor en base a su id
>>> Author.objects.get(pk=1)
<Author: milmazz>

# Creamos par de artículos
>>> article1 = Post(author=author, title="Article 1")
>>> article1.save()

>>> article2 = Post(author=author, title="Article 2")
>>> article2.save()

>>> for article in Post.objects.all():
     print("%s by %s" % (article.title, article.author))
Article 1 by milmazz
Article 2 by milmazz

# Eliminamos el autor
>>> author.delete()

>>> for article in Post.objects.all():
    print("%s by %s" % (article.title, article.author))
Article 1 by None
Article 2 by None

Un segundo ejemplo, ahora haciendo uso del valor SET() en el argumento on_delete:

# models.py
from django.db import models
from django.contrib.auth.models import User

def get_superuser():
    return User.objects.get(pk=1)

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET(get_superuser))
    title = models.CharField(max_length=128)

    def __unicode__(self):
        return self.title

Nuestra sesión interactiva con el API sería similar a la siguiente:

$ python manage.py shell
>>> from ondelete.models import Post
>>> from django.contrib.auth.models import User

>>> User.objects.all()
[<User: milmazz>]
# Creamos un nuevo usuario
>>> author = User(username='milton')
# Guardamos el objeto en la base de datos,
# de manera explícita al invocar el método save()
>>> author.save()
# Vista de los usuarios registrados en la base de datos
>>> User.objects.all()
[<User: milmazz>, <User: milton>]

# Creamos par de artículos
>>> article1 = Post(user=author, title="Article 1")
>>> article1.save()

>>> article2 = Post(user=author, title="Article 2")
>>> article2.save()

>>> for article in Post.objects.all():
     print("%s by %s" % (article.title, article.user))
Article 1 by milton
Article 2 by milton

# Eliminamos el usuario 'milton'
>>> author.delete()

>>> for article in Post.objects.all():
    print("%s by %s" % (article.title, article.user))
Article 1 by milmazz
Article 2 by milmazz