Chrome, proxys y formularios inseguros

El título de esta entrada se parece «curiosamente» al título de una película de miedo… Y al principio eso es lo que es, ya que desde el lunes estoy recibiendo esta pantalla en mi chrome cada vez que meto una incidencia en mi redmine:

El caso es que luego le dabas al botón de enviarlo de todas formas y la cosa funcionaba normalmente… Eso si, era sumamente molesto y tenía clientes que accedían a poner sus incidencias y no era plan tener que explicarles a todos que no es que ahora fuésemos menos seguros que antes, sino que chrome había activado una funcionalidad nueva.

Esa funcionalidad, que estaba prevista para chrome 86, pero que se activó con chrome 87 hace que cuando los datos de un formulario se envíen a una página que no tiene https activado se de la advertencia de que cualquiera podría interceptar los datos… Me parece muy buena advertencia, pero ¡Espera! todo el tráfico de mis webs ya va por https, ¿cómo es que recibo esa advertencia?

Según la especificación de chrome esto solo debía pasar si el formulario llevaba a una página no segura, así que revisé las etiquetas form y vi que eran rutas relativas (y si es relativa dentro de una página https debería llevar a una página https), así que decidí revisar el camino completo usando el inspector de chrome donde, efectivamente, vi que la petición se realizaba a una página https pero descubrí que el campo Location que aparecía en los headers de la petición venía como http y no https, ¿cómo podía ser?

Pues resulta, después de mucho buscar, que la gestión del https la estaba haciendo mediante un proxy inverso en apache y, parece ser, que la directiva ProxyPassReverse modifica el host del Location, pero no el protocolo. Por suerte esto tiene fácil solución y basta con incluir esto en el VirtualHost:

RequestHeader set X-Forwarded-Proto "https"

Antes de hacerlo no os olvidéis de añadir el módulo:

sudo a2enmod headers

Con esto ya conseguiremos que el proxy no confunda a chrome y que desaparezca el molesto mensaje…

Medio siglo

Hoy (ayer para cuando salga este post) cumplo 50 años, no es que quiera que me felicitéis ni nada, la verdad a mi los cumpleaños solo me sirven para medir el tiempo… Y resulta que llevo medio siglo ya en este mundo. Si miramos un poco atrás, a finales del siglo XIX la esperanza media de vida era de 48 años y menos si nos echamos un poco más para atrás. Así que si yo fuese un habitante de la tierra de hace doscientos años estaría ya al final de mi vida útil.

Esto da que pensar, aunque considero que todavía me quedan muchísimas cosas que hacer y mucho que aportar todavía, quizá este pequeño hito (y el hecho de que Google se haya caído a nivel mundial) me sirvan para recordar un poco cómo han cambiado las cosas desde hace medio siglo hasta ahora. De hecho, simplemente viendo cómo han cambiado mis hábitos ya se puede uno hacer una idea de lo mucho que ha cambiado nuestro entorno en unas pocas décadas.

Lo más importante, para mi, es el hecho de contar con internet. Si, cuando yo estudiaba no había internet, de hecho yo fui uno de los pioneros en tener correo electrónico ya en 1988 (estaba en un grupo de investigación de la facultad) y pasé gran parte de mis estudios en la facultad de informática asistiendo al despliegue cada vez mayor de la red de redes. Hasta la llegada de la web (no confundir con internet, que se tardaron unos añitos en inventar el http, html y los navegadores) cuando querías estar informado de algo tenías que recurrir a la biblioteca (si, con sus fichitas en cartulina y eso), a los documentales de la tele (si, alguno se veía entonces también) o a las revistas especializadas. Tengo que decir que yo no era un gran fan de la biblioteca y sin embargo devoraba las revistas de informática (y hasta conseguí ser el editor de la revista del club de informática de mi facultad.

El problema con internet ha sido que la información que se encuentra se ha vuelto demasiado abundante y muy poco contrastada. Las fake news y el dar pábulo a cualquier cantamañanas ha convertido la mayor y mejor tecnología que el ser humano haya desarrollado hasta el momento en un arma de destrucción masiva. Una pena.

Otra cosa que ha cambiado, y mucho, mis hábitos y los de todo el mundo es la llegada del teléfono móvil. Tuve que esperar a 1996 para tener mi primer teléfono móvil y eso me cambió la vida. Nunca más quedarse tirado en la carretera sin poder avisar a nadie, nunca más no saber dónde esta alguien a quien esperas, etc… Todo ventajas, eso si, el precio de la época no era de lo mejor que te podías encontrar y el GPRS que era lo mejor que podías conseguir en datos tampoco es que fuese demasiado útil para transferir datos, pero nada que un SMS no pudiese arreglar. Después ya vinieron los smartphones, el 3G, el 4G y ahora el 5G… Y todo el mundo con su móvil en el bolsillo enganchado a internet.

Los ordenadores, que me deslumbraron desde que mi padre se compró su primer VIC-20, que él usaba para jugar al ajedrez y que yo programaba con sus 3KB de memoria, se han convertido actualmente en una commodity. No hay evolución real en su arquitectura sino que seguimos la ley de moore, frenada por la avaricia empresarial o la oportunidad comercial. He tenido un commodore VIC20, un amstrad cpc 64, un PC compatible con un NECV20 y todos los siguientes procesadores intel y amd (incluso cyrix), pero al final ya todo se resume en una arquitectura fija donde cambia lo comprimidos que están los elementos y el coste de lo que queramos gastarnos. El formato: portatil, sobremesa, da igual, quien no tiene hoy en día un ordenador se queda sin poder hacer muchas cosas… Pero todo cambiará con la computación cuántica, ¿o no?

En estos años he vivido momentos históricos: la llegada de la democracia, la caída del muro de Berlín, crisis importantes: la burbuja .com, las subprime y ahora la covid-19 (entre otras), donde parece que siempre se avanza para encontrarse un obstáculo mayor. No me puedo quejar de cómo he superado estas crisis, pero lo que si me han ayudado es a averiguar un poco mejor quienes son buenas personas y quienes simplemente están por el interés… Aunque últimamente casi empiezo a pensar que merecemos la extinción como especie por nuestra cerrazón.

He sido aficionado a pocas cosas, una de ellas – la fotografía – ha cambiado demasiado para mi gusto. Estuve muchos años con una cámara reflex completamente manual (una Zenit 12) con un objetivo de 45mm aprovechando cada foto (revelarlas era caro de narices), cuando tuve un poco de dinero me compré una DSLR Nikon D15 que me dió muchísimas satisfacciones hasta que me cargué la película reflectante del visor intentando limpiarla; pero con el advenimiento de las cámaras mejores y el almacenamiento casi ilimitado de fotografías digitales ya no me merece la pena sacar la D90 por la que la sustituí y me tengo que conformar con hacer los miles de fotos que todo el mundo saca con su móvil en la mano. Ahora parece que solo usan cámaras «profesionales» los youtubers o instragrammers que, aunque no tienen ni idea de hacer fotografía, a base de captar adeptos han conseguido un material que saca las mejores imágenes por ellos.

En fin, no sabemos lo que nos deparará el futuro y solo nos queda aferrarnos a las cosas que nos ofrecen algo, un sentimiento, una evocación, una posibilidad de desarrollo y no soltarla hasta que encontremos otra que lo supere… Así que, ¡vamos a por el otro medio siglo!

Añadir https y let’s encrypt a tu aplicación con docker

Una vez que hemos empezado a «dockerizar» aplicaciones, y antes de saltar al siguiente nivel (kubernetes por ejemplo) nos encontramos con la necesidad de pasar a produccion las aplicaciones que vamos desarrollando y, quizá, utilizar un gestor como Kubernetes nos haga más complicado utilizar https. Hay dos soluciones principales que he utilizado para distintos servicios y que os voy a comentar muy brevemente aquí: usar apache como proxy inverso instalado en la máquina host o utilizar un docker con el proxy inverso en nginx.

Para los dos casos vamos a suponer que tenemos un contenedor docker con una aplicación web que tenemos expuesto en el puerto 8080 (por ejemplo).

Método 1: Apache Nativo

Empecemos por el primer sistema, el que primero se me ocurrió y que tiene sus ventajas y sus inconvenientes. Básicamente consiste en instalar de manera nativa el servidor apache, el módulo mod_proxy y hacer que actúe como un proxy inverso para los dominios que necesitemos. Os explico los pasos suponiendo que estáis instalando en una máquina ubuntu recien provisionada:

sudo apt-get install -y apache2 libapache2-mpm-itk
sudo a2enmod rewrite
sudo ufw allow in "Apache Full"
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install -y python-certbot-apache
sudo service apache2 restart

Llegados a este paso debemos crear un archivo de configuración para la aplicación web y dejarlo en /etc/apache2/sites-available/misitio.conf algo como esto:

<VirtualHost *:80>
	ServerName www.misitio.com
	AssignUserId miusuario miusuario

	ServerAdmin [email protected]
	DocumentRoot /home/miusuario/web

	<Directory /home/miusuario/web>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>
</VirtualHost>

Lo relevante es el nombre del sitio y un directorio para las páginas, que no vamos a utilizar, pero que tiene que existir para las validaciones posteriores. En este caso estoy usando también el módulo itk para que se ejecute con un usuario sin privilegos. Posteriormente a esto ejecutamos:

sudo a2ensite misitio
sudo service apache2 restart

Con esto ya tendremos el servicio apache levantado y respondiendo a peticiones, por lo que podemos solicitar el certificado (recuerda que el dns del servicio debe apuntar a la dirección IP del servidor).

sudo certbot

Esto nos preguntará qué dominios queremos proteger y si todo ha ido bien nos generará un archivo midominio-le-ssl.conf que contendrá ya los enlaces a los certificados y configuración asociada. Con lo que ya podrías acceder a https://www.midominio.com

Ahora queda la parte en la que «conectamos» este servidor con nuestro docker que, recordemos, está corriendo en el puerto 8080, para ello modificaremos el archivo de configuración que nos ha creado certbot añadiendo estas líneas:

ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/

Reiniciamos apache y ya tenemos enganchado nuestro docker a https.

Método 2: docker que nos lo hace todo

Si el método anterior nos parece un poco pesado o no queremos tener que guardar la configuración particular de una máquina, podemos optar por añadir esto a nuestro archivo docker-compose (teniendo en cuenta que hemos llamado miservicio al servicio que tenemos en el 8080):

    https-portal:
        image: steveltn/https-portal:1
        depends_on:
            - miservicio
        ports:
            - 80:80
            - 443:443
        restart: always
        volumes:
            - ./ssl_certs:/var/lib/https-portal
        environment:
            DOMAINS: 'www.midonio.com -> http://miservicio:8080 #production' 

Y eso es todo, el servidor al levantarse se encarga de pedir los certificados y guardarlos en el directorio ssl_certs que será el único que tenemos que persistir para evitar tener que pedirlos cada vez que arranque el servidor.

Cada uno de los dos métodos tiene sus pros y sus contras (hacerlo en kubernetes es otra historia y no aplica ninguno de estos métodos), pero básicamente si queremos exponer más de un contenedor (en distintos docker-compose) la única manera es usar el proxy inverso nativo, si todo lo que queréis servir por https está en un solo docker-compose el segundo método es mucho más cómodo.

Monta tu propio cluster Kubernetes

Llevo los últimos meses intentando aprender Kubernetes después de que la experiencia con Docker fuese tan satisfactoria en todos los aspectos. No obstante con Kubernetes caía una y otra vez en los problemas de la complejidad inherente a una plataforma tan adaptada para los pasos a producción de grandes aplicaciones. Muchos de los tutoriales (incluyendo los propios de kubernetes) te instaban a instalarte minikube o usar algunos playgrounds disponibles online como Katacoda o Play with kubernetes. Al final lo que era evidente es que necesitaba un cluster k8s para poder aprender un poco más de kubernetes.

Minikube tiene importantes restricciones y los otro playground son de usar y tirar, por lo que, al final, si quería aprender de verdad tenía que construirme mi propio cluster… Y ahora mismo no me apetece pagar por tener algo puramente experimental, así que aproveché y, dado que tengo dos sobremesa en casa, decidí instalar el cluster en mis propios ordenadores y poder disfrutar de toda la potencia de kubernetes. Aquí un resumen muy resumido de lo que hay que hacer en ubuntu 18.04 (que es lo que tenía en los dos):

Primer paso: instalar docker

Eso creo que ya lo hemos tratado aquí en algunas ocasiones, no obstante ha mejorado mucho la forma de instalarlo desde entonces (en todas las máquinas):

sudo apt install docker.io
sudo systemctl enable docker
sudo addgroup docker ${USER}

Paso 2: instalar kubernetes

Igualmente en todos los nodos:

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
sudo apt-get install kubeadm kubelet kubectl
sudo apt-mark hold kubeadm kubelet kubectl

Si todo ha ido bien podemos ver la versión que hemos instalado:

kubeadm version

Paso 3: inicializar cluster

Para inicializar el cluster primero debemos asignar un nombre a cada nodo, además, previamente tendremos que desactivar el swap que no se lleva bien con este sistema, primero con el master y luego con el resto:

sudo swapoff -a
sudo hostnamectl set-hostname master-node

Y luego en el resto:

sudo hostnamectl set-hostname worker01

Con todos los nodos ya con nombre podemos inicializar en el maestro el cluster:

sudo kubeadm init --pod-network-cidr=10.244.0.0/16

Como resultado (y si todo va bien) el comando nos devolverá el comando a ejecutar en cada uno de los nodos, algo así como:

kubeadm join --discovery-token abcdef.1234567890abcdef --discovery-token-ca-cert-hash sha256:1234..cdef 1.2.3.4:6443

Debemos guardar ese comando ya que lo tendremos que ejecutar posteriormente en cualquier nodo que queramos unir al cluster

Para poder administrar con kubectl necesitamos guardar la configuración que acabamos de generar en el usuario que estemos usando… Dentro del master es sencillo:

kubernetes-master:~$ mkdir -p $HOME/.kube
kubernetes-master:~$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
kubernetes-master:~$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

Con esto ya podremos lanzar nuestros comandos kubectl contra nuestro nuevo cluster

Paso 4: Desplegar red en el cluster

Tal como está configurado ahora mismo no hay forma de comunicarse entre los pods y el resto, vamos a instalar flannel como red virtual, para ello ejecutar:

sudo kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

Al cabo de un rato podremos ver si los pods están correctamente desplegados con:

kubectl get pods --all-namespaces

Paso 5: añadir nodos a la red

Como ya dijimos en el paso 3, tenemos un comando a ejecutar en cada nodo de la red para unirse al cluster que acabamos de crear. Ejecutamos ese comando en cada uno de los nodos que queremos unir y luego, dentro del master, podemos comprobar si están presentes todos los nodos y el estado en que están:

kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-node Ready master 3d17h v1.18.5
worker01 Ready 3d17h v1.18.5
worker02 Ready 2d22h v1.18.5
worker03 Ready 2d17h v1.18.5

Si todos están en estado Ready hemos triunfado… Listos para desplegar lo que queramos en nuestro cluster casero… A ver si nos da tiempo a explorarlo con cierta extensión.

Variables de entorno y docker

Después de un tiempo definiendo distintos contenedores docker y coordinando despliegues con docker compose me he encontrado con circunstancias que me obligaban a modificar los archivos de definición cada vez que necesitaba hacer un nuevo despliegue o paso a producción y eso, bueno, eso es un poco molesto. Así que sabiendo que la gente que ha desarrollado todo esto era más lista que yo me puse a buscar cómo proponen que lo hagamos.

Y la respuesta es… Mediante varibles de entorno (al menos una de ellas), así que vamos a ver cómo podemos, como ejercicio, poner el número de versión de nuestro despliegue como varible de entorno..

Uno de mis docker-composes tenía este aspecto:

    web-php:
        container_name: web-php
        build: .
        image: nexus.biblioetech.com/biblioeteca/nomorepass:1.0.0
        ports:
            - "80:80"
        environment: 
            DBHOST: "db"
        links:
            - "web-mysql:db"

Como véis el número de versión acompañaba al nombre de la imagen y esto es así para que al construirla ya llevase la etiqueta adecuada para subirla al repositorio privado. El problema era que cada vez que hacíamos una release nueva teníamos que tocar este archivo a mano… Si queremos no tener que tocar el archivo tenemos que poner algo así:

    web-php:
        container_name: web-php
        build: .
        image: nexus.biblioetech.com/biblioeteca/nomorepass:${VERSION}
        ports:
            - "80:80"
        environment: 
            DBHOST: "db"
        links:
            - "web-mysql:db"

De esta forma solo hay que definir la varible de entorno VERSION al valor que acabamos de generar. Ahora bien, es fácil que nos olvidemos de asignar esta variable de entorno si no está en ningún sitio del repositorio, así que lo más sencillo es incluirla en un archivo .env que será el que docker-compose cargue antes de ejectuar el build… Y quedaría así:

VERSION=1.0.0

En este archivo podemos incluir todas las variables que necesitemos y, lo que es más, podremos pasarselas al Dockerfile si lo necesitamos, eso si, el método es un poco más rebuscado (eso lo veremos un poco más adelante).