En el último post, en donde comencé a explorar el uso de Traefik en Producción, podemos encontrar una serie de recetas/archivos que deberían permitir instanciar un blog con WordPress en pocos minutos.
Si revisamos el docker-compose.yml usado para desplegar (o disponibilizar) Traefik, en la parte de los labels encontraremos dos muy específicas:
- traefik.http.routers.traefik.middlewares=auth-traefik
- traefik.http.middlewares.auth-traefik.basicauth.users=usuario:contraseña_cifrada
Haciendo uso del middleware BasicAuth, encontramos un usuario y una contraseña que, en el ejemplo mostrado, harán que el acceso al Dashboard de Traefik necesite de esos datos para autenticarse y poder tener acceso.
Ahora bien, además de usar la opción users, podemos usar userFile. La diferencia principal es que esta opción nos permite también tener una lista de usuarios en un archivo externo a nuestro docker-compose.yml. Esto podría traducirse, según el caso (y dependiendo del TOC de cada uno), en una mejor legibilidad y mantenibilidad (?) de nuestros archivos/recetas.
Si volvemos sobre el ejemplo compartido en el post anterior, vemos que sólo para el container de Traefik estoy usando autenticación (con la opción users). El docker-compose.yml en cuestión es:
version: '3'
services:
traefik:
restart: always
image: traefik:2.10
container_name: traefik
ports:
- "443:443"
- "80:80"
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/etc/traefik/letsencrypt
- ./traefik.yml:/etc/traefik/traefik.yml
- ./log:/var/log
labels:
- traefik.enable=true
- traefik.http.routers.traefik.rule=Host(`monitor.dominio.com.ar`)
- traefik.http.routers.traefik.service=api@internal
- traefik.http.routers.traefik.tls=true
- traefik.http.routers.traefik.tls.certresolver=letsencrypt
- traefik.http.routers.traefik.middlewares=auth-traefik
- traefik.http.middlewares.auth-traefik.basicauth.users=usuario:contraseña_cifrada
networks:
- web
networks:
web:
external: true
Y el docker-compose.yml usado de ejemplo para levantar un sitio con WordPress fue:
version: "3"
networks:
web:
external: true
internal:
external: false
services:
blog:
image: wordpress:6.3.0-apache
container_name: blog
restart: always
env_file:
- ./blog.env
labels:
- traefik.enable=true
- traefik.http.routers.blog.rule=Host(`blog.dominio.com.ar`)
- traefik.http.routers.blog.tls=true
- traefik.http.routers.blog.tls.certresolver=letsencrypt
- traefik.port=80
cap_drop:
- SETGID
- SETUID
networks:
- internal
- web
depends_on:
- database
database:
image: mariadb:10.11
container_name: database
restart: always
env_file:
- ./blog.env
networks:
- internal
labels:
- traefik.enable=false
volumes:
- ./mysql:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
env_file:
- ./blog.env
labels:
- traefik.enable=true
- traefik.http.routers.phpmyadmin.rule=Host(`phpmyadmin.dominio.com.ar`)
- traefik.http.routers.phpmyadmin.tls=true
- traefik.http.routers.phpmyadmin.tls.certresolver=letsencrypt
- traefik.port=80
networks:
- internal
- web
depends_on:
- database
volumes:
mysql:
Momento de dar contexto al ejemplo que va a explicar lo que vamos a hacer aquí con las autenticaciones.
El objetivo será tener un nuevo middleware que usará para autenticar (únicamente) el container de PhpMyAdmin. Lo haré de esta manera para no compartir las credenciales con el Dashboard de Traefik, pero además para tener una lista de usuarios que podrán autenticarse (pensando que a futuro puedo necesitar impedir a un usuario en particular el acceso a ese servicio, a nivel de container).
Como me voy a concentrar sólo en el container de PhpMyAdmin, voy a seguir el ejemplo sólo con los labels de ese container (al final tendremos el archivo completo).
Lo primero que hago es agregar el nuevo middleware, usando un nombre bien claro (aquí): nuevo-middleware-de-autenticacion
labels:
- traefik.enable=true
- traefik.http.routers.phpmyadmin.rule=Host(`phpmyadmin.dominio.com.ar`)
- traefik.http.routers.phpmyadmin.tls=true
- traefik.http.routers.phpmyadmin.tls.certresolver=letsencrypt
- traefik.port=80
- traefik.http.routers.phpmyadmin.middlewares=nuevo-middleware-de-autenticacion
- traefik.http.middlewares.nuevo-middleware-de-autenticacion.basicauth.usersfile=./archivo_de_usuarios
Y al final defino para ese middleware el uso de BasicAuth con la opción UsersFile, con el path a mi archivo.
Si ahora intento recrear mis containers, veremos que el container de PhpMyAdmin no va a funcionar y que si miramos el middleware en el Dashboard veremos algo así:
Lo que nos pasa, más allá de lo obvio, es que Traefik no encuentra el archivo que le estamos diciendo que debe usar. Para ahorrar algo de tiempo, voy a saltar a la explicación del problema y su solución (sin pasar por las varias pruebas que tuve que hacer hasta que llegué a la solución).
El primer detalle a considerar aquí es que la documentación no es lo suficientemente clara (o al menos se presta a confusión). El path al archivo debe ser indicado en relación al container de Traefik y no del container del servicio que quiere usarlo o del lugar físico en el host.
Esto quiere decir que el primer cambio debemos aplicarlo sobre el docker-compose.yml de Traefik (recordar que estamos basando todo sobre el ejemplo de este post).
Supongamos que creamos entonces, dentro del mismo directorio en donde tenemos los archivos/recetas de Traefik, un archivo (o varios si vamos a usar diferentes middlewares para distintos containers) al que llamaremos autenticacion (o lo que quieran) con algo como:
usuario1:$apr1$B95Jx5XI$0xeUpcNoepwMgJ2h2uLJe0
usuario2:$apr1$CVa1azC3$Stcv7.43rKES6X74sz6Sw1
En donde el par usuario y contrseña es usuario1:password1 y usuario2:password2.
Vamos a cambiar el docker-compose de Traefik.
version: '3'
services:
traefik:
restart: always
image: traefik:2.10
container_name: traefik
ports:
- "443:443"
- "80:80"
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/etc/traefik/letsencrypt
- ./traefik.yml:/etc/traefik/traefik.yml
- ./log:/var/log
- ./autenticacion:/etc/traefik/autenticacion
labels:
- traefik.enable=true
- traefik.http.routers.traefik.rule=Host(`monitor.dominio.com.ar`)
- traefik.http.routers.traefik.service=api@internal
- traefik.http.routers.traefik.tls=true
- traefik.http.routers.traefik.tls.certresolver=letsencrypt
- traefik.http.routers.traefik.middlewares=auth-traefik
- traefik.http.middlewares.auth-traefik.basicauth.users=usuario:contraseña_cifrada
networks:
- web
networks:
web:
external: true
En la lista de volúmenes, en la última línea, estoy montando el archivo que acabo de crear y lo mapeo con /etc/traefik/autenticacion dentro del container de Traefik (es importante no confundir este detalle, ya que es Traefik el que debe poder leer el archivo).
Ahora volvamos sobre la receta del blog y cambiemos los labels de PhpMyAdmin.
version: "3"
networks:
web:
external: true
internal:
external: false
services:
blog:
image: wordpress:6.3.0-apache
container_name: blog
restart: always
env_file:
- ./blog.env
labels:
- traefik.enable=true
- traefik.http.routers.blog.rule=Host(`blog.dominio.com.ar`)
- traefik.http.routers.blog.tls=true
- traefik.http.routers.blog.tls.certresolver=letsencrypt
- traefik.port=80
cap_drop:
- SETGID
- SETUID
networks:
- internal
- web
depends_on:
- database
database:
image: mariadb:10.11
container_name: database
restart: always
env_file:
- ./blog.env
networks:
- internal
labels:
- traefik.enable=false
volumes:
- ./mysql:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
env_file:
- ./blog.env
labels:
- traefik.enable=true
- traefik.http.routers.phpmyadmin.rule=Host(`phpmyadmin.dominio.com.ar`)
- traefik.http.routers.phpmyadmin.tls=true
- traefik.http.routers.phpmyadmin.tls.certresolver=letsencrypt
- traefik.port=80
- traefik.http.routers.phpmyadmin.middlewares=nuevo-middleware-de-autenticacion
- traefik.http.middlewares.nuevo-middleware-de-autenticacion.basicauth.usersfile=/etc/traefik/autenticacion
networks:
- internal
- web
depends_on:
- database
volumes:
mysql:
Al recrear tanto el container de Traefik como el de PhpMyAdmin, veremos que el middleware ya no presenta errores.
Además, la autenticación debería responder correctamente (y de forma independiente de la definida para el Dashboard de Traefik).