Provisioning Virtual Machines with Vagrant and Ansible

In a previous post I showed how to create and provision a VM for development purposes using Vagrant, and the benefits of being able to replicate a consistent environment with a single command. But Vagrant can also create several VMs with a single command and connect them by private networks, allowing to test complete infrastructure setups.

Ansible

As an Infrastructure as Code (IaC) tool, Ansible has a similarity to Vagrant. But Ansible is much more powerful and is widely used in production environments to manage baremetal and virtualized hosts running Linux, Unix or Windows, both on-premises and in the cloud.

Ansible works by connecting from a Control Node where Ansible is installed, to Managed Nodes where the configuration is applied. Ansible does not need to be installed in the Managed Nodes, it simply connects to them via SSH for Linux and Unix hosts and Windows Remote Management (WinRM) for Windows hosts. The only requirements are Python in Linux and Unix hosts and PowerShell in Windows hosts.

Using Vagrant together with Ansible

Vagrant supports several provisioners including Ansible. There are two different Ansible provisioners in Vagrant: Ansible and Ansible Local. The Ansible provisioner runs Ansible from your guest, while Ansible Local installs Ansible in a VM provisioned by Vagrant (Control Node) and uses it to configure other VMs (Managed Nodes). Since Ansible cannot run on Windows and I want to keep the requisites in your guest machine limited to Vagrant and VirtualBox, we’re going to use Ansible Local.

The Setup

We are going to configure two Nginx web nodes and a load balancer that will distribute requests to those two nodes in a round-robin manner. In addition we’ll create a fourth Ansible Control node.

Vagrantfile

Choose an empty directory and create the following Vagrantfile:

Vagrant.configure("2") do |config|
    config.vm.box = "bento/ubuntu-18.04"

    config.vm.define "lb" do |machine|
        machine.vm.network "private_network", ip: "172.17.177.21"
        machine.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
    end

    config.vm.define "node1" do |machine|
        machine.vm.network "private_network", ip: "172.17.177.22"
    end

    config.vm.define "node2" do |machine|
        machine.vm.network "private_network", ip: "172.17.177.23"
    end

    config.vm.define "controller" do |machine|
        machine.vm.network "private_network", ip: "172.17.177.11"

        machine.vm.provision "ansible_local" do |ansible|
            ansible.playbook = "provisioning/playbook.yml"
            ansible.limit = "all"
            ansible.inventory_path = "provisioning/hosts"
            ansible.config_file = "provisioning/ansible.cfg"
        end

        machine.vm.synced_folder ".", "/vagrant", mount_options: [ "umask=077" ]
    end
end

First we define a load balancer (lb) node and connect it to private_network with an IP address. We also forward port 8080 in our host machine to port 80 in the VM, so we can access it through our browser.

Then we define two web nodes (node1 and node2) and join them to private_network with an IP address. These nodes have no port forward so they are not accessible through our browser.

Finally we define the Ansible controller (controller) that is going to be used by Vagrant to configure the other nodes. We join it to private_network with an IP. We use the ansible_local provisioner as discussed before, indicating that we want to run the playbook on all hosts (ansible.limit = "all") and indicate the path to the playbook, inventory and ansible.cfg files. Finally we override the default configuration for the synced_folder, using a umask to remove permissions from all users except vagrant. This is necessary otherwise both Ansible and ssh will complain for security reasons and fail.

Ansible configuration

Create a provisioning directory where we’ll place all Ansible related files. By default Vagrant autogenerates an inventory that is placed in the guest VM under the path /tmp/vagrant-ansible/inventory/vagrant_ansible_local_inventory, but since we have no name resolution we cannot use it. Instead create a hosts file under the provisioning directory:

controller ansible_connection=local
 lb         ansible_host=172.17.177.21 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/lb/virtualbox/private_key
 node1      ansible_host=172.17.177.22 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node1/virtualbox/private_key
 node2      ansible_host=172.17.177.23 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node2/virtualbox/private_key
 [nginx]
 lb
 node[1:2]

This file is telling Ansible how to connect to the hosts. It lists the IP addresses that we defined in Vagrantfile and the private keys to connect to every host. Vagrant places the private keys under .vagrant/machines/<machine name>/virtualbox/private_key paths. We also define an nginx group which consists of the load balancer and both web nodes.

The next file to create is the Ansible Playbook (playbook.yml) which tells Ansible which tasks to execute in which hosts:

---
- hosts: nginx
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
      become: yes

- hosts: node1
  tasks:
    - name: Copy hello from node 1
      ansible.builtin.copy:
        dest: /var/www/html/index.html
        content: 'Hello from Node 1!'
      become: yes

- hosts: node2
  tasks:
    - name: Copy hello from node 2
      ansible.builtin.copy:
        dest: /var/www/html/index.html
        content: 'Hello from Node 2!'
      become: yes

- hosts: lb
  tasks:
    - name: Copy nginx.conf to load balancer
      ansible.builtin.copy:
        src: nginx.conf
        dest: /etc/nginx/sites-enabled/default
      become: yes
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
      become: yes
      

To keep it simple we go in a linear fashion: first use apt to install nginx in the nginx group from the inventory (lb, node1 and node2). Then we copy a welcome message to node1 and node2 (/var/www/html/index.html). Then override the nginx default configuration in the load balancer (/etc/nginx/sites-enabled/default) with the content of nginx.conf, and restart the service to load the new configuration.

Next, inside the provisioning directory, create a files directory and create the nginx.conf file inside of it:

upstream hello {
    server 172.17.177.22;
    server 172.17.177.23;
}

server {
    listen 80;

    location / {
        proxy_pass http://hello;
    }
}

This file configures nginx in the lb node as a load balancer for the two web nodes. It defaults to round robin.

Finally we’ll create the ansible.cfg file inside the provisioning directory to allow ssh to connect to the controlled nodes:

[defaults]
host_key_checking = no

You should have a directory structure like this:

It’s time to start it! Open a terminal where Vagrantfile is placed and enter:

vagrant up

Wait while Vagrant creates 4 virtual machines, installs Ansible in the controller node and runs the playbook to configure the load balancer and both web nodes.

Now go to http://localhost:8080 and you will see the welcome message from node1:

Reload the page several times and you will see the message change as the load balancer forwards the requests to node1 and node2 alternatively.

Wrapping up

Enter vagrant halt to stop the VMs and save some resources, or vagrant destroy -f to delete them, concluding this demo.

Vagrant is a very nice way to test with virtual machines. It can create a single VM or several VMs connected by virtual networks.

Integration with Ansible allows to test Ansible playbooks in your machine. It also supports other provisioners like Chef, Puppet, Docker and more, enabling the development of complex setups in a virtual environment, without the need for real servers.

Creating and provisioning virtual machines with Vagrant

Often we need to create local environments to test setting up new services. A great way to do it is by provisioning VMs with Vagrant.

What is Vagrant?

Vagrant is a cross-platform tool to automate the creation and management of VMs for development uses. You write the desired configuration for your VMs in a Vagrantfile and then use the vagrant command to start, stop and manage the VMs.

Installing Vagrant

Vagrant only automates the management of VMs. The VMs themselves are hosted by a provider like VirtualBox, VMware, Docker or Hyper-V. For this tutorial we are going to use VirtualBox on Windows 10.

Install VirtualBox

Vagrant requires a compatible version of VirtualBox. Download and install VirtualBox 6.1.16.

Install Vagrant

Download and install Vagrant 2.2.10. This version is compatible with the previously installed VirtualBox. Restart your machine.

Starting and provisioning a simple VM

First choose a working directory and then create a Vagrantfile by entering in a command line:

vagrant init bento/ubuntu-20.04

What we’ve done here is tell the vagrant command to create a Vagrantfile using the bento/ubuntu-20.04 box. VM images for Vagrant are called boxes, and you can find them here. I recommend to use the bento ones.

Let’s have a look at the Vagrantfile. Open it in an editor, I recommend to open the directory in Visual Studio Code:

The Vagrantfile is a description of how you want Vagrant to create your VMs. It’s written in Ruby, but you don’t need to know Ruby to edit it. The syntax is self-explanatory and has very good comments. If you removed all the comments this would be the entire file:

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-20.04"
end

It simply telling Vagrant to create a VM using the bento/ubuntu-20.04 box. It uses the Vagrant configuration format version “2” (the current one).

Let’s make it more interesting. Uncomment lines 66-69 by removing the leading hashes, they should look like this:

As the comments explain, this is telling Vagrant to run a script to install Apache in the VM. This provisioning script runs only the first time you start the VM. If you want to run provisioning in an already existing VM, you need to use the vagrant provision command.

Let’s also uncomment line 31 to tell Vagrant to map the VM’s por 80 to port 8080 in our machine:

Now start the VM! In the same directory where the Vagrantfile was created, enter in the command line:

vagrant up

You will see some logs and wait while Vagrant downloads the box and starts the VM, maps some ports and runs the provisioning script to install Apache. When it’s done, you can enter vagrant status to check that it was created correctly:

If you open VirtualBox you can also verify that your VM is running in VirtualBox:

Connecting to the Virtual Machine

Enter in the command line:

vagrant ssh

This uses ssh to connect to the VM. Now you are inside the VM in a Linux terminal:

Change directories to /vagrant and create a new file:

cd /vagrant
echo "Hello from Vagrant!" > hello.txt

Now open the file you created inside the VM in Visual Studio Code:

Vagrant by default enables file sharing between your host and the guest VM, synchronizing your working directory (the one where Vagrafile is) to the /vagrant directory in the VM.

And if you point the browser in your host machine to http://localhost:8080/, you will see the welcome page from the Apache server that is running inside the guest VM. Great!

Stopping and deleting the VM

To disconnect from the VM, press Ctrl + D or type exit at the command line. Now enter the following to stop the VM:

vagrant halt

If you want to delete the VM because you no longer need it or you want to start fresh:

vagrant destroy -f

If you didn’t provide the -f argument, it would ask for confirmation before deleting the VM. There are many other commands which you can discover by typing vagrant without arguments and by reading the documentation.

Conclusion

As you can see, Vagrant is a great tool for creating test environments in your machine. The real power comes from being able to share the Vagrantfile with other developers whom can then recreate the same environment in a Windows, Linux or Mac OS machine by just typing vagrant up.

In a next post we’ll explore a more advanced scenario involving several VMs connected via a virtual network.

Configurando Javadoc en Eclipse 2018-09 y Ubuntu 18.04 con OpenJDK 11

Si instalaste el paquete default-jdk en Ubuntu 18.04, por estas fechas (septiembre 2018) obtendrás el paquete openjdk-11-jdk. Es un poco confuso porque hasta septiembre de 2018 lo que en realidad instala es Java 10:

Captura de pantalla de 2018-09-22 18-32-27Sólo después de septiembre de 2018 se va a instalar Java 11 con el paquete openjdk-11-jdk.

También podrás comprobar que en Eclipse no está funcionando el Javadoc al hacer hover sobre las clases. Para solucionar el problema vamos a Window -> Preferences -> Java -> Installed JREs y seleccionamos el JDK “11”:

Captura de pantalla de 2018-09-22 18-48-29

Hacemos click en Edit, abrimos los detalles y seleccionamos Javadoc location:

Captura de pantalla de 2018-09-22 18-50-51

La URL es http://docs.oracle.com/javase/10/docs/api/. Asegúrate de usar http, no https. Puedes usar el botón Validate para comprobar la URL:

Captura de pantalla de 2018-09-22 18-53-22

También podemos configurar las sources para poder verlas desde Eclipse. Primero hay que instalar el paquete:

sudo apt install openjdk-11-source

Luego seleccionamos Source attachment en la ventana Edit JRE e ingresamos el path de las fuentes:

/usr/lib/jvm/openjdk-11/lib/src.zip

Captura de pantalla de 2018-09-22 19-25-54

Captura de pantalla de 2018-09-22 19-27-51

Captura de pantalla de 2018-09-22 19-38-24

De esta forma tenemos todo configurado y cuando hagamos hover vamos a ver el Javadoc y con F3 u Open Declaration las fuentes de la class library.

 

Problema de conexión Wi-Fi en Linux y laptops Lenovo

Si tienes una portátil Lenovo e instalaste o estás tratando de instalar Linux, puede que te hayas encontrado con desagrado que la conexión Wi-Fi no se puede activar. He observado este problema tanto en Ubuntu como en Fedora.

Para solucionarlo hagamos una prueba ingresando lo siguiente en la terminal:

sudo modprobe -r ideapad_laptop

Si funciona vas a ver que de inmediato aparece la conexión disponible. Una vez terminada la instalación de Linux, o si ya lo habías instalado, hacemos que este cambio sea permanente ingresando en la terminal:

echo "blacklist ideapad_laptop" | sudo tee -a /etc/modprobe.d/ideapad.conf

Los constantes problemas de Linux en el escritorio

Este problema se debe a un bug que ha permanecido durante años en el driver ideapad-laptop. Desafortunadamente este tipo de problemas sigue siendo muy común en Linux, impidiendo que se convierta en una buena alternativa, además gratuita, para el común de los usuarios.

Por eso es importante que como programador siempre tengas en cuenta que la finalidad de un sistema informático es facilitar las tareas al usuario, a quien poco le importa la belleza de la arquitectura ni la pureza de tu código si tu programa es difícil de usar y se convierte en un obstáculo más que en un facilitador para la solución de sus problemas.

Instalando un entorno de programación C en Windows con Mingw-w64 y Visual Studio Code

Windows no es un sistema operativo que cuenta con un buen soporte para la programación en C. Por razones que no quedan del todo claras, Microsoft se ha negado rotundamente a actualizar el soporte del compilador C de Visual Studio, el cual ha quedado estancado en el estándar C89.

Afortunadamente la comunidad Open Source viene al rescate con el proyecto Mingw-w64, que provee el compilador GCC, el debugger GDB, binutils, más las headers y bibliotecas necesarias para producir binarios nativos de Windows. Mingw-w64 es ampliamente utilizado y actualizado constantemente con las últimas versiones de GCC y tecnologías como OpenGL y DirectX.

Hay varias formas de obtener Mingw-w64, pero una de las mejores es usando MSYS2 (Minimal SYStem 2), una línea de comandos y entorno de programación que usaremos para instalar Mingw-w64.

1. Instalando MSYS2

Para comenzar, vamos a msys2.github.io y descargamos el instalador de 64 bits (el que dice “x86_64”) que nos permitirá compilar programas nativos de 32 y 64 bits. Abrimos el archivo y lo instalamos con todas las opciones por defecto. Si al final dejamos tildada la opción “Run MSYS2 now“, vamos a ver la consola de MSYS2 MSYS que luce de esta manera:

msys2-console

MSYS2 usa el gestor de paquetes pacman de Arch Linux para instalar y actualizar el paquetes. Hay que seguir las instrucciones en la página de MSYS2 para actualizar el entorno. Al momento de escribir este artículo (MSYS2 versión msys2-x86_64-20160921) hay que ingresar en la consola (atento a las mayúsculas):

Puede que en alguno de los siguientes pasos aparezca un mensaje pidiendo cerrar la consola. En ese caso debemos cerrar la ventana por la equis y volver a abrirla desde el menú inicio, buscando MSYS2 MSYS.

pacman -Sy pacman
pacman -Syu
pacman -Su

MSYS2 se instala por defecto en el directorio c:\msys64. En el menú inicio de Windows podremos ver que en la carpeta MSYS2 64bit aparecen 3 consolas, lo cual se presta a cierta confusión. Las tres abren una consola bash pero con diferentes variables de entorno establecidas para diferentes usos:

  • MSYS2 MSYS: es la consola de MSYS que utilizaremos para correr pacman e instalar paquetes. Se puede compilar programas aquí pero los mismos dependerán de msys-2.0.dll, una capa de emulación POSIX. Basta decir que no utilizaremos ésta para compilar.
  • MSYS2 MinGW 32bit: provee un entorno de programación para compilar programas nativos de Windows de 32 bits que dependen de MSVC runtime.
  • MSYS2 MinGW 64bit: éste es el entorno de programación que usaremos para compilar programas nativos de Windows de 64 bits.

2. Instalando Mingw-w64

Ahora sí instalaremos Mingw-w64. MSYS2 sólo provee la consola bash y otras herramientas auxiliares, para obtener el compilador debemos instalar Mingw-w64.

Al momento de escribir este artículo había un bug en MSYS2 (versión msys2-x86_64-20160921, corregido en versiones posteriores) por lo tanto debemos ir al directorio C:\msys64 y crear dentro un directorio llamado “mingw64“.

Para descargar e instalar el paquete, abrimos la consola de MSYS2 MSYS e ingresamos:

pacman -S mingw-w64-x86_64-toolchain

Con esto hemos instalado dentro de MSYS2 la “toolchain” necesaria para compilar programas, que incluye binutils, make, pkg-config, y el compilador gcc.

Podemos cerrar la ventana de MSYS2 MSYS, ir al menú inicio y abrir la consola MSYS2 MinGW 64-bit (no MSYS2 MSYS). Si todo salió bien, al ingresar “gcc -v” veremos la versión del compilador:

gcc-version

Finalmente agregamos la ruta de MinGW al path de Windows. Para ello abrimos una consola de línea de comandos (Menú inicio -> cmd) e ingresamos setx path “%path%;C:\msys64\mingw64\bin”:

path

3. Instalando y configurando Visual Studio Code

Una vez instalado el paquete mingw-w64-x86_64-toolchain, es perfectamente posible compilar programas desde la consola de MSYS MinGW 64-bit, usando la interfaz de línea de comandos para invocar comandos como cc y make.

Sin embargo, si queremos un entorno mucho más amigable, podemos usar Visual Studio Code, el IDE de Microsoft que se presenta como la alternativa ligera y open source a Visual Studio.

Debemos ir a code.visualstudio.com, descargar el instalador y seguir las sencillas instrucciones. Luego debemos crear un directorio en nuestro disco que utilizaremos para el primer proyecto en C. A diferencia de Visual Studio, Visual Studio Code se basa en directorios y no en proyectos, por lo que debemos ir al menú Archivo -> Abrir carpeta y seleccionar el directorio vacío que creamos.

A continuación abrimos la Paleta de comandos (Ctrl + Shift + P), escribimos Extensiones: Instalar Extensiones y presionamos Enter. Esto nos abre el Panel de extensiones en el cual vamos a buscar cpptools, la extensión de Microsoft que agrega soporte para C y C++. Debemos instalarla (haciendo click en Instalar) y habilitarla (haciendo click en Recargar).

En el Explorador de archivos a la izquierda, creamos un archivo llamado hello.c en el cual ingresaremos el código del clásico Hello World:

vs-code1

Guardamos el archivo (Ctrl + S), hacemos click para poner un breakpoint en la línea 6, y luego situando el cursor en la línea 1, que aparece subrayada en verde, vemos que aparece un foco amarillo al cual haremos click para configurar el include path. Esto crea en nuestro directorio un archivo c_cpp_properties.json que indica a la extensión cpptools dónde están los headers de C. Debemos editar la sección Win32 de dicho archivo y cambiar el valor del includePath al directorio C:/msys64/mingw64/include (ingresar tal cual) Guardamos el archivo (Ctrl + S).

c_cpp_properties

Para configurar la compilación del proyecto abrimos la Paleta de comandos (Ctrl + Shift + P) y buscamos Tareas: Configurar ejecutor de tareas, seleccionando la opción Otros. Esto crea un archivo tasks.json que editaremos de la siguiente manera:

tasks

Guardamos el archivo (Ctrl + S). Lo que hemos hecho aquí es decirle a Visual Studio Code dónde encontrar el compilador cc que instalamos previamente en MSYS2. Los parámetros indican agregar símbolos de depuración para gdb, el nombre de nuestro archivo de fuente y el nombre del archivo de salida.

Para compilar presionamos Ctrl + Shift + B, si todo salió bien veremos que nuestro ejecutable hello.exe aparece en el explorador de archivos. Sino aparecerá un mensaje de error en el panel Salida y debemos verificar si configuramos bien la ruta y los parámetros.

Finalmente ejecutaremos nuestro programa usando dbg, el clásico debugger de GNU. Debemos abrir la barra lateral de depuración (Ctrl + Shift + D o haciendo click en el insecto), luego hacer click en el icono de la rueda para configurar, y seleccionar la opción C++ (GDB/LLDB). Esto crea un archivo launch.json que editaremos para indicar las rutas de nuestro ejecutable y del debugger. En la configuración C++ Launch hay que editar la propiedad program y agregar la propiedad miDebuggerPath con la ruta de gdb, como se muetra a continuación:

launch

Guardamos el archivo (Ctrl + S), y presionamos F5 o hacemos click en la flecha verde para ejecutar. Si todo salió bien, debe aparecer la consola de nuestro programa y veremos que Code se detiene en el breakpoint que establecimos anteriormente. La barra lateral muestra las variables locales, una sección de inspección y la pila de llamadas. Abajo aparece la consola de depuración que permite enviar comandos a gdb, y arriba está la barra para controlar la ejecución, con los mismos atajos de teclado que Visual Studio:

vs-code2

Como vemos, Visual Studio Code es un entorno muy agradable de usar y bastante completo, ya que a pesar de ser nuevo (apareció en 2015) se ha desarrollado con bastante rapidez. Perfecto para aprender y dar los primeros pasos programando en C bajo Windows.

Próximos pasos

Hemos configurado Visual Studio Code para compilar, ejecutar y depurar de forma visual y sencilla un programa con un sólo archivo fuente, usando directamente el compilador cc. El próximo paso que podrías seguir es editar el archivo tasks.json para usar make en vez de cc y poder compilar programas más complejos (en MinGW el ejecutable se llama mingw32-make.exe). ¿Puedes descubrir cómo?

El rol del idioma inglés

¿Se puede ser un buen programador sin hablar inglés?

Es una pregunta interesante. Supongo que sí, pero en mi experiencia personal el no saber inglés hubiera resultado una desventaja importante.

El hecho es que el inglés, por distintas razones, es el idioma universal de las ciencias y los negocios, como en un momento lo fue el francés. El inglés te abre las puertas a un mundo de 1500 millones de hablantes que producen las innovaciones científicas y tecnológicas más importantes.

Las últimas investigaciones, estándares, documentaciones y libros se publican en su mayoría absoluta en inglés, y lamentablemente muchos nunca son traducidos. Si quieres participar en un equipo de trabajo internacional, en un proyecto open source, un foro o una lista de correos, el inglés también es un requisito fundamental.

La dificultad del inglés

¿Es dificil aprender inglés? Yo diría que sí, porque al ser un lenguaje germánico tiene muchas diferencias con el español en su sintaxis, semántica, estructura de la oración, conjugación de los verbos… Aprender inglés es aprender otra forma de pensar.

Ni hablar del “slang“, esas expresiones propias de cada idioma y cada región que no tienen una traducción literal, como hanging out – ¿¿colgando afuera?? No, pasando el rato –. Sólo se pueden ir aprendiendo con el tiempo y la práctica

Tal vez lo más complicado del inglés es la pronunciacion. A diferencia del español, el inglés no tiene una pronunciación fija, sino que el sonido de una letra cambia dependiendo de las letras que la preceden y la siguen, así que no queda más remedio que ir aprendiendo de memoria las cientos de combinaciones posibles. Para complicar más las cosas, la pronunciación varía mucho entre el inglés americano y el británico, e incluso entre distintas regiones de un mismo país.

Por otro lado, hay cosas que son más fáciles que el español. Por ejemplo, no existe la tilde, así que no tienes que preocuparte por escribir bien los acentos. Y la mayoría de las palabras son de género neutro (the house, the sun, the sea, the city), otra cosa menos que memorizar.

Recursos para aprender

Por suerte son muchos los recursos que hay para aprender. Lo mejor son los cursos presenciales. Una parte importante de mi aprendizaje fueron los cursos que recibí como parte de mi educación secundaria y universitaria. El gobierno de la ciudad de Buenos Aires ofrece cursos gratuitos de idiomas en el programa Lenguas en los Barrios, y hay infinidad de academias de idiomas, muchas dedicadas exclusivamente al inglés.

También hay muchos cursos online gratuitos que puedes conseguir con cualquier buscador y son muy buenos. La desventaja es que no tienes un profesor que te guíe y corrija en cosas como la pronunciación, y además requieren mucha disciplina para seguir el curso hasta el final.

Otra opción muy buena son las aplicaciones como Duolingo, el cual recomiendo altamente, ya que es excelente para aprender la pronunciación y memorizar las palabras. Duolingo está disponible para iPhone, Android, Windows Phone, y además la página web tiene funciones extra como explicaciones y tablas de conjugación que no están presentes en la aplicación móvil.

Todo complementado siempre por la práctica constante. La lectura, la música y la televisión ayudan muchísimo a la memoria, por lo que es imprescindible incorporarlas a un aprendizaje integral.

Conceptos básicos de criptografía: cifrado simétrico

Hace ya bastante tiempo, por la década de los 1990, la web apenas comenzaba y solía suceder que un programa podía tener una vida feliz sin tener ninguna función de criptografía.

Pero hoy cualquier programa medianamente complejo necesita en algún momento comenzar a utilizar el cifrado para proteger los datos del usuario en su almacenamiento o transmisión en la red, sin embargo es un tópico que sigue siendo confuso para muchos programadores cuando lo enfrentan por primera vez.

Cifrado César

Un algoritmo de cifrado simétrico usa la misma clave para cifrar y descifrar los datos. Uno de los primeros algoritmos conocidos fue el Cifrado César, llamado así porque fue usado por el mismísimo Julio César hace más de dos mil años en comunicaciones militares. Para ello, sustituía cada letra del alfabeto con la letra tres lugares más adelante. Al llegar a la Z, volvía a comenzar por la letra A, igual que las agujas de un reloj, en lo que se llama aritmética modular. Un ejemplo de este cifrado es el siguiente:

Original: Z A P A T O
Cifrado:  C D S D W R
Clave:    3

Aparentemente el Cifrado César tuvo éxito en su momento, ocultando los mensajes de los espías enemigos. Hoy no es más que un juego de niños, aunque increíblemente hay criminales con pocas luces que sigen cayendo por usarlo.

Vigenère

Una mejora al cifrado César fue el cifrado Vigenère, atribuido a Blaise Vigenère pero que realmente fue inventado por Giovan Battista Bellaso en 1553. En vez de sustituir todas las letras por un número fijo, se usa una palabra como clave que indica cuántos lugares se mueve cada letra. Si el mensaje es más largo que la clave, ésta se repite. Por ejemplo utilizando la clave “ABCD” que indica mover cada letra 1, 2, 3 y 4 lugares:

Original: Z A P A T O
Clave:    A B C D A B
          1 2 3 4 1 2
Cifrado:  A C S E U Q

En el ejemplo de cifrado César vimos que la letra D se repetía dos veces, proporcionándonos la información de que se trata de una letra frecuentemente usada en el idioma de mensaje, posiblemente una vocal. Eso sumado a las pocas combinaciones posibles de claves hace trivial su descifrado.

El cifrado Vigenère oculta mucho mejor la información del mensaje original, representó un reto durante siglos y su dificultad aparente le ganó el apodo de le chiffre indéchiffrable. Pero ya para el siglo 19 comenzó a ser roto rutinariamente, y una computadora moderna lo puede romper en milisegundos.

One-time pad

¿Qué pasa si en vez de usar una palabra repetida, usamos letras al azar? Por ejemplo:

Original: Z A P A T O
Clave:    K M W B T P
Cifrado:  K N M C N E

Este pequeño cambio, aunque parezca increíble, ha vuelto nuestro mensaje inviolable. ¿Por qué? Porque si cada letra de la clave es aleatoria, y todas las letras de la clave son aleatorias, el texto cifrado también es completamente aleatorio, independientemente de cual haya sido el mensaje original.

De hecho, podemos probar todas las claves posibles de 6 letras y “descifrar” las palabras ZAPATO, CONEJO, PIEDRA, PELOTA, o cualquier otra combinación posible de 6 letras, y ninguna tiene mayor posibilidad que otra de ser la correcta. Sólo la combinación del texto cifrado y la clave correcta permiten dilucidar el mensaje original.

Más aún, ni siquiera conocer parte del mensaje original permite descifrar las partes faltantes, porque el resto del texto cifrado sigue siendo completamente aleatorio. Si a esto añadimos la posibilidad de paddings o mensajes falsos, entonces se vuelve imposible determinar la longitud exacta del mensaje original o si existe siquiera un mensaje real.

Este tipo de cifrado se llama One-time pad, y fue Claude Shannon el primero en demostrar su seguridad perfecta, invulnerable a las computadoras más poderosas aunque se disponga de tiempo infinito.

La administración de las claves

Entonces, ¿por qué no utilizamos todos el one-time pad? De hecho este sistema fue usado por la KGB durante la guerra fría. Y también fue violado.

Aunque es teóricamente perfecto, el One-time pad tiene severas limitaciones prácticas. Primero necesitas una clave aleatoria. Y golpear teclas “al azar” en tu teclado no es el “aleatorio” que estamos buscando, ya que si lo haces durante mucho tiempo inevitablemente empezarán a aparecer patrones repetidos debido a la forma de tu teclado y tus dedos, memoria muscular, etc. Varios One-time pad fueron descifrados debido a defectos en la fuente de aleatoriedad.

Además, la clave debe ser por lo menos tan larga como el mensaje, así que si cifras un mensaje de 1 GB, necesitas generar una clave de 1 GB que ahora tienes que mantener a salvo para que no caiga en las manos de un adversario o se pierda y con ella tus datos desaparezcan para siempre.

Finalmente, como las claves deben ser aleatorias, nunca se pueden reusar. La KGB proporcionaba a sus espías libros de claves en miniatura, que tenían que leer con una lupa. Como era tan inconveniente llevar cada vez más libros encima para cada mensaje, algunos se vieron tentados a reutilizarlos, y de esta forma sus mensajes pudieron ser descifrados.

Y de esto se desprende una lección muy importante: la administración de las claves es tan importante como el algoritmo en un esquema criptográfico. De nada vale tener el algoritmo más sofisticado si las password se almacenan o se transmiten sin cifrar, o si la password de tus usuarios es “12345” [Youtube], reusan la misma en cada sitio cuestionable, o la anotan en un papelito debajo del teclado (cosa que he visto personalmente). Además, siempre está el viejo método de la llave inglesa para “extraer” información.

AES

De esta forma llegamos a los algoritmos de cifrado modernos. Son algoritmos sofisticados que permiten usar un password en vez de un enorme número aleatorio y proveen de mucha más seguridad que un cifrado simple como el César o el Vigenère. Algunos de los más usados son RC4, 3DES, Twofish, Serpent, Blowfish, y finalmente AES, elegido en 2001 como estándar internacional.

El nivel de seguridad de estos algoritmos es enorme. Si se usó  una buena clave, romper por fuerza bruta un mensaje cifrado con AES llevaría millones de veces más tiempo que la edad actual de universo, lo que se denomina computacionalmente imposible.

Aquí podemos elaborar lo que llamaremos la regla de oro de la criptografía: a menos que seas un experto en criptografía de talla mundial y las redes de Feistel sean un tema de conversación diario con tus amigos, nunca implementes tu propio algoritmo de cifrado. Las agencias de inteligencia emplean a algunos de los matemáticos más brillantes del mundo, con acceso a las supercomputadoras más poderosas y fábricas de circuitos especializados. Romper algoritmos amateur es una de las cosas que los chicos de la NSA hacen para divertirse durante el almuerzo.

Crear un algoritmo de cifrado es un tema fuera del alcance del común de los mortales, e incluso los expertos tienen que enviar sus algoritmos a largos procesos de peer review para que otros expertos los analicen en búsqueda de vulnerabilidades, antes de que pasen a ser de uso general.

Y a menos que sepas realmente lo que estás haciendo, tampoco intentes programar tu propia implementación de un algoritmo conocido. Aunque esté perfecto, podría fallar en implementar contramedidas contra ataques de side-channel, el los que se emplea información como los patrones de uso del CPU o las emisiones electromagnéticas del sistema causadas por el algoritmo y que revelan información del mensaje o de la clave. Es el tipo de cosas para las que la NSA tiene presupuesto ilimitado.

En su lugar, usa siempre implementaciones de algoritmos como AES, que sean ampliamente conocidas, probadas y soportadas, prestando siempre atención al eslabón más débil de la cadena: el usuario.

Cómo instalar phpMyAdmin en Ubuntu Xenial

Recientemente tuve que configurar phpMyAdmin en Ubuntu 16.04 (Xenial) y encontré varios problemas que no le dejaban funcionar, así que decidí elaborar esta ayuda para quien se encuentre perdido en la misma tarea.

Lo primero que necesitas son los pre requisitos. Asumiendo que empezamos de cero y queremos usar Apache como servidor web:

sudo apt install apache2
sudo apt install php
sudo apt install libapache2-mod-php

Para instalar MySQL (en el medio pedirá una contraseña para el usuario root):

sudo apt install mysql-server

Para instalar phpMyAdmin hay que escribir:

sudo apt install phpmyadmin

Y ahora llegamos a la parte crítica. En el medio se ejecuta un asistente que pide una contraseña para configurar el usuario administrativo, y luego aparece una pantalla donde pide seleccionar el servidor web a configurar, donde PARECE que Apache está seleccionado pero NO lo está.

Instalación de phpMyAdmin, pantalla de Configuración del servidor web Apache

Es fundamental apretar espacio para seleccionar Apache y asegurarse de que aparezca un asterisco marcando la selección.

Luego de finalizar el asistente, podemos ir en nuestro navegador a localhost/phpmyadmin para ver la consola en toda su gloria:

Mensaje de error de phpMyAdmin, falta el paquete mbstring (multibyte strings) de PHP

Ups… parece que algo salió mal. Por suerte es fácil de resolver. phpMyAdmin necesita el paquete mbstring pero no lo instaló con el resto de las dependencias. Lo instalamos con

sudo apt install php-mbstring
sudo service apache2 restart

Y presionamos F5 en el navegador para encontrarnos con… una página en blanco.

Fue aquí donde me tomó más tiempo porque no hay indicación alguna del error. Finalmente encontré la solución y la confirmación del bug. Otro paquete faltante en las dependencias. Lo instalamos:

sudo apt install php-gettext

Presionamos nuevamente F5 y ahora sí, la consola de phpMyAdmin en nuestro navegador:

Consola de administración de phpMyAdmin

Intercambiando dos variables

Digamos que tenemos dos variables (a, b) y queremos intercambiarlas, es decir que una tome el valor de la otra:

int temp = a;
a = b;
b = temp;

Nada más fácil, ¿verdad? Creamos una variable temporal para poder resguardar el valor de a mientras asignamos a = b, y luego asignamos a b el valor previo de a.

Es necesario crear esta variable temporal para no perder el valor de la primera variable que intercambiamos… ¿o no? Qué tal esto:

a = a ^ b;
b = a ^ b;
a = a ^ b;

Si ejecutamos el fragmento de código en C podemos comprobar que los valores de a y b han sido intercambiados sin haber usado nunca una variable intermedia. ¿Qué está pasando?

La operación OR exclusivo (^) tiene un par de propiedades interesantes que están actuando en este caso. Un número combinado con sí mismo es igual a cero (a ^ a = 0), y un número combinado con cero es igual a sí mismo (a ^ 0 = a).

En la primera línea estamos asignando a = a ^ b. Luego en la segunda línea estamos asignando b = a ^ b, pero recordemos que ahora a = a ^ b, por lo tanto queda b = a ^ b ^ b.

b ^ b se cancela, por lo que efectivamente en la segunda línea estamos asignando b = a.

Igualmente en la tercera línea asignamos a = a ^ b, pero recordemos que antes de la asignación a contiene el valor a ^ b, y b contiene el valor de a, por lo tanto estamos asignando a = a ^ b ^ a, efectivamente a = b.

Este algoritmo se llama XOR swap. Y no es que tenga mucha utilidad práctica, pero es una linda curiosidad del mundo de la computación.

Administrando servicios de Windows

Recientemente tuve que programar una pequeña utilidad para administrar un servicio de Windows, así que pensé recoger aquí algunas informaciones que se encuentran dispersas en varias fuentes para facilitar la tarea.

La manera más fácil de administrar un servicio en .NET es mediante la clase ServiceController del assembly System.ServiceProcess.dll. El siguiente ejemplo muestra cómo reiniciar el servicio de impresión (Spooler):

using System;
using System.ServiceProcess;

namespace ServiceControllerExample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine(&quot;Reiniciando servicio Spooler&quot;);

                ServiceController serviceController = new ServiceController(&quot;Spooler&quot;);

                serviceController.Stop();
                serviceController.WaitForStatus(ServiceControllerStatus.Stopped);

                serviceController.Start();
                serviceController.WaitForStatus(ServiceControllerStatus.Running);

                Console.WriteLine(&quot;Servicio reiniciado, presione Enter para finalizar.&quot;);
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }
    }
}

Para instanciar ServiceController simplemente debemos usar el nombre del servicio que lo podemos obtener de la consola de administración (services.msc). Si nos equivocamos con el nombre el objeto será creado igual, y los métodos para cambiar el estado pueden fallar por varias razones, así que es una buena idea poner un try…catch alrededor de todo.

Para reiniciar hay que llamar a Stop() y luego a Start(). El servicio debe estar en un estado previo que tenga sentido para la operación. Por ejemplo si llamamos a Start() en un servicio que ya está iniciado se producirá una excepción.

Otra cosa que hay que notar son las llamadas a WaitForStatus(). Los métodos Start() y Stop() son no bloqueantes, así que debemos hacer esto para sincronizar nuestro código.

Obteniendo el directorio de un servicio

Si desde tu servicio accedes a archivos, la primera vez puede llegarte a sorprender que todas las rutas relativas apuntan a c:\windows\system32. Así que para obtener el working directory del servicio hay que usar AppDomain.CurrentDomain.BaseDirectory.

Para obtener el working directory desde otro proceso hay un par de maneras. Mediante Windows Management Instrumentation (WMI), o directamente desde la clave HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\NombreDelServicio del registro.

RegistryKey key = Registry.LocalMachine.OpenSubKey(@&quot;System\CurrentControlSet\Services\Spooler&quot;);
string servicePath = (string)key.GetValue(&quot;ImagePath&quot;);