EDIT: Como han comentado, es verdad que no puse la fuente porque lo dejé un poco a medias. Me basé casi al completo en esto:
Bueno lo prometido es deuda y algunos me pedisteis si os podía poner en un hilo como publicar contenedores Docker con SSL a través de Traefik pues aquí va. Lo primero lo que vamos a conseguir:
- Traefik2 publicado en nuestro dominio actuando de proxy inverso sirviendo todo el contenido/servicios a través de SSL gracias a Let's Encrypt. Para que os hagáis una idea podréis publicar por ejemplo un cliente bittorrent vía bt.dominio.com
- El certificado Let's Encrypt se renovará automáticamente.
- Además como en este mundo hay mucho hacker que le gusta tocar las pelotas, vamos a meterle Authelia para lograr OAuth y restringir más el acceso.
- Os dejaré un ejemplo de Heimdall (página dashboard) y de qBittorrent publicado para veáis que queda bastante apañado el asunto.
Requisitos
- Servidor o PC que vayamos a tener prestando el servicio. Yo uso un servidor casero con Ubuntu que me monté por piezas con placas chinas y un XEON pero vaya que esto son Dockers, lo podéis montar en una Raspberry sin problemas.
- Docker y docker-compose instalado
- Port Forwarding en vuestro router del puerto 443 y 80 (opcional) hacia la máquina dónde pongáis Traefik
- Dominio propio. Vale 10€ o menos no me seáis ratas... se puede montar con servicios DynDNS (DuckDNS, etc) pero entonces hay que pasar a usar formato "dominio.com/servicio" y es bastante más coñazo.
- Gestor de DNS para poder publicar los nombres de los servicios. Yo voy a usar Cloudflare que es gratuito y además ofrece una CDN bastante apañado y la posiblidad incluso de no usarla si quieres. Si usáis Cloudflare deberéis configurarlo con la SSL/TLS "Completo"
Que no vas a aprender
- Comprar un dominio y configurarlo en sitios como CloudFlare.
- Hacer Port-Forwarding del puerto 443 a la máquina servidor dónde publiques Traefik.
- Instalar Docker
Paso 1 - Preparar entorno de trabajo
Si no habéis usado docker antes deberíais saber que al final estos contenedores son muy de quita y pon y como no tengas cuidado acabas con el sistema operativo hecho unos zorros con carpetas de dockers desperdigadas. Así que lo mejor es que empecéis por crearos una carpeta "docker" y le demos los permisos adecuados. Es decir:
/home/USER/docker
mkdir ~/docker
sudo setfacl -Rdm g:docker:rwx ~/docker
sudo chmod -R 775 ~/docker
Vale, de esta manera tendremos todo reunido dentro de la carpeta docker. Esta carpeta contendrá en su interior una carpeta por cada uno de los servicios. Es decir, en nuestro ejemplo acabaremos teniendo 5 carpetas en docker:
- traefik2
- qbittorrent
- authelia
- heimdall
- shared - Esta no es ningún servicio sino que simplemente es una carpeta "común" que usaran todos los dockers.
Además, como vamos a usar docker-compose necesitamos crear un fichero "docker-compose.yml" donde pondremos toda la configuración y luego un fichero ".env" (sí con "." por delante) para guardar variables comunes que usaremos en "docker-compose.yml".
touch docker-compose.yml
touch .env
En .env meteremos lo siguiente que son las variables que usarán todos los servicios dockers que montemos por lo general
PUID=1000 # User ID del usuario que ejecutará los servicios dockers
PGID=1000 # Group ID del usuario
TZ=Europe/Madrid # Timezone que usarán los dockers
USERDIR=/path/a/la/carpeta/docker # Path del usuario que ejecutará los servicios dockers
Para hacer uso de una variable de ".env" en "docker-compose.yml" se hace con $NOMBREVARIABLE
Paso 2 - Configurando Traefik2
2.1 Entendiendo Traefik
Si habéis usado Traefik antes pues me imagino que sabréis que la versión 2 introdujo un montón de cambios respecto a la 1. En concreto cambian tanto que al final trabajan con 3 releases diferentes:
- chevrotin = 2.2.X - La actual y la que vamos a usar
- cantal = 2.1.X - La primera versión de la 2 que ya la están descontinuando
- maroilles = 1.7.X - Ya sólo la actualizan para temas de seguridad
Traefik2 trabaja principalmente con 3 conceptos: Routers, Middlewares y Services
- Routers: Son el frontend por así decirlo, son los que escuchan las peticiones que llegan a traefik, los puntos de entrada y donde se definen las reglas que se aplican a las peticiones
- Services: Son el backend, identifican hacia donde van las peticiones. Aquí es donde se define por ejemplo que qbittorrent está escuchando en el puerto 8080 y cosas así.
- Middlewares: Es la característica reina de traefik2. Son los que hacen la verdadera función de proxy inverso modificando las peticiones añadiéndoles cosas como paths, cabeceras específicas, etc.
2.2 Creando autenticación básica y el entorno de traefik2
Aunque acabaremos con Authelia, primero habrá que empezar con un acceso básica usuario:contraseña. Para ello crearemos un fichero ".htpasswd" usando HTPASSWD Generator y lo guardaremos en "$USERDIR/docker/shared/.htpasswd" El formato es sencillo:
nombreusuario:contraseña
Aquí entra Cloudflare. Como vamos a tener Let's Encrypt + Cloudflare generando los certificados es necesario crear 3 variables en ".env"
DOMAINNAME=example.com
[email protected]
CLOUDFLARE_API_KEY=XXXXXXXXXXXX
La API Key de Cloudflare se consigue en la sección "Tokens API" de nuestro perfil en Cloudflare.
Necesitamos esta información porque vamos a usar el método "DNS-Challenge" para la generación del certificado wildcard que vamos a generar en LetsEncrypt.
Por último, creamos las carpetas del servicio Traefik y el fichero acme.json que guardará la información del certificado SSL que crearemos con los permisos adecuados. También de paso, como nos gusta guardar todo lo que pasa pues creamos el fichero de log de traefik2.
mkdir traefik2
mkdir traefik2/acme
touch traefik2/acme/acme.json
chmod 600 traefik2/acme/acme.json
touch traefik2/traefik.log
mkdir traefik2/rules
2.3 Configuración de la Red docker
Todo los servicios que creemos irán alojados en una red propia dentro de la máquina donde ejecutamos Docker. Algo así como una LAN dentro de la LAN de nuestra casa. Esta red la creamos así
docker network create --gateway 10.10.10.1 --subnet 10.10.10.0/24 t2_proxy
Una vez creada la red es necesario incluirla en la configuración de "docker-compose.yml"
version: "3.7"
########################### NETWORKS
networks:
t2_proxy:
external:
name: t2_proxy
default:
driver: bridge
########################### SERVICES
services:
2.4 Creación del servicio traefik2 en docker-compose.yml
Podría daros todo el texto y ya pero lo voy a documentar un poco. Tenemos que meter todo esto a partir de la siguiente línea tras "services:" (cuidado con los espacios y tabulaciones!)
# Traefik 2 - Reverse Proxy
traefik:
container_name: traefik
image: traefik:chevrotin # the chevrotin tag refers to v2.2.x
restart: unless-stopped
Primero la parte sencilla, creamos el servicio y lo configuramos con nombre "traefik" que se usará la imagen docker "chevrotin" que es la correspondiente a la versión 2.2.X. Además indicamos que salvo que paremos nosotros el docker, este debe reiniciarse siempre.
command: # CLI argument
- --global.checkNewVersion=true
- --global.sendAnonymousUsage=true
- --entryPoints.http.address=:80
- --entryPoints.https.address=:443
# Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/
- --entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/12,172.64.0.0/13,131.0.72.0/22
- --entryPoints.traefik.address=:8080
- --api=true
#- --api.insecure=true
#- --serversTransport.insecureSkipVerify=true
- --log=true
- --log.level=ERROR # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
- --accessLog=true
- --accessLog.filePath=/traefik.log
- --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines
- --accessLog.filters.statusCodes=400-499
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.defaultrule=Host(`{{ index .Labels "com.docker.compose.service" }}.$DOMAINNAME`)
- --providers.docker.exposedByDefault=false
- --providers.docker.network=t2_proxy
- --providers.docker.swarmMode=false
- --providers.file.directory=/rules # Load dynamic configuration from one or more .toml or .yml files in a directory.
#- --providers.file.filename=/path/to/file # Load dynamic configuration from a file.
- --providers.file.watch=true # Only works on top level files in the rules folder
- --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt Staging Server - uncomment when testing
- --certificatesResolvers.dns-cloudflare.acme.email=$CLOUDFLARE_EMAIL
- --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
- --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare
- --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
Empezamos a complicar las cosas. Al docker le decimos que ejecute traefik utilizando una serie de comandos que configuran así a grosso modo:
- Que traefik escuchará en el puerto 80 y 443
- Que el log tendrá un nivel de "DEBUG", es decir todo. Tranquilos luego lo cambiamos. Además le decimos el buffer de líneas, la ruta del fichero y demás
- trustedIPs indica las IPs en las que confiaremos para acceder a Traefik. Esto es importante ya que si no metemos aquí las IPs de Cloudlare no conseguiremos saber la IP real de quien accede a Cloudflare
- providers.file.directory es importante ya que indicaremos el path donde estarán las reglas de Traefik2.
- Las líneas de certificatesResolvers configuran la forma en la que crearemos el Certificado SSL. Como he comentado antes usaremos DNS Challenge. Es importante que al principio para evitar problemas la línea "certificatesResolvers.dns-cloudflare.acme.caServer" NO esté comentada. Así usaremos el entorno de pruebas de Let's Encrypt y no nos banearán por hacer muchas peticiones. Cuando ya tengamos el certificado funcionando la comentaremos.
networks:
t2_proxy:
ipv4_address: 10.10.10.2
security_opt:
- no-new-privileges:true
Configuramos Traefik para que utilice la network que creamos antes y además le indicamos que tenga como IP la 10.10.10.2. Lo de security_opt es para que el docker no pueda escalar privilegios.
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 8080
published: 8080
protocol: tcp
mode: host
Aquí indicamos los puertos del docker y como los usará. 80 y 443 es para acceder a los servicios y el 8080 es para la API.
volumes:
- $USERDIR/docker/traefik2/rules:/rules
- /var/run/docker.sock:/var/run/docker.sock:ro
- $USERDIR/docker/traefik2/acme/acme.json:/acme.json
- $USERDIR/docker/traefik2/traefik.log:/traefik.log
- $USERDIR/docker/shared:/shared
Esto indica las carpetas/ficheros de nuestra de las que hará uso el docker. Al final el docker no deja de ser un contenedor aislado con su propia estructura de carpetas y ficheros en su interior y si queremos que use algo "extra" debemos indicárselo. Le indicamos los ficheros de los que hemos hablo antes y la carpeta shared.
environment:
- CF_API_EMAIL=$CLOUDFLARE_EMAIL
- CF_API_KEY=$CLOUDFLARE_API_KEY
Aquí le pasamos la información de CloudFlare para que pueda crear el certificado SSL correctamente.
2.4 Labels de Traefik2
Con todo lo anterior ya podríamos ejecutar el docker pero no podríamos acceder a él desde un navegador porque todavía no hemos configurado la otra gran gracia de Traefik, las "Labels" que son básicamente la configuración del servicio por detrás de Traefik, es decir, el router, middleware y service. Vamos poco a poco porque son unas pocas.
labels:
- "traefik.enable=true"
# HTTP-to-HTTPS Redirect
- "traefik.http.routers.http-catchall.entrypoints=http"
- "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
Traefik.enable indica que este service docker hace uso de traefik, es decir que va a estar "proxificado". Las siguientes líneas configuran un "router" que es un redirect automático de HTTP a HTTPS de todas las peticiones que llegue a Traefik y cualquier de sus servicios. Ya que ponemos un certificado SSL que mínimo que usarlo siempre.
# HTTP Routers
- "traefik.http.routers.traefik-rtr.entrypoints=https"
- "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME`)"
- "traefik.http.routers.traefik-rtr.tls=true"
- "traefik.http.routers.traefik-rtr.tls.certresolver=dns-cloudflare" # Comment out this line after first run of traefik to force the use of wildcard certs
- "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME"
- "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME"
#- "traefik.http.routers.traefik-rtr.tls.domains[1].main=$SECONDDOMAINNAME" # Pulls main cert for second domain
#- "traefik.http.routers.traefik-rtr.tls.domains[1].sans=*.$SECONDDOMAINNAME" # Pulls wildcard cert for second domain
Ahora un "router" para configurar este service concreto, el de traefik. Primero le indicamos que sólo escuche HTTPs, luego le indicamos que el service escucha bajo el Host traefik.$DOMAINNAME. Esto es que si vuestro dominio es example.com para llegar a traefik deberéis poner en el navegador "traefik.example.com"
Luego indicamos que use sólo TLS para que sea seguro.
tls.certresolver=dns-cloudflare" es importante. Esta label sólo la usaremos la primera vez para generar el certificado wildcard *.$DOMAINNAME, luego la comentaremos y así sólo usaremos un sólo certificado y no uno diferente para cada servicio como indicamos con las labels "main" y "sans". También os dejo como poner más certificados en caso de que trabajéis con más de uno.
## Services - API
- "traefik.http.routers.traefik-rtr.service=api@internal"
## Middlewares
- "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file"
Aquí indicamos el servicio de la API y el middleware. Si os fijáis en middleware pone "middlewares-basic-auth@file" eso es porque vamos a hacer uso de un fichero en el que vamos a reunir todas las reglas middleware. Esto lo haremos creando el fichero "middlewares.toml" en la carpeta "rules".
touch traefik2/rules/middlewares.toml
Lo rellenamos de la siguiente manera:
[http.middlewares]
[http.middlewares.middlewares-basic-auth]
[http.middlewares.middlewares-basic-auth.basicAuth]
# username=user, password=mystrongpassword (listed below after hashing)
# users = [
# "user:$apr1$bvj3f2o0$/01DGlduxK4AqRsTwHnvc1",
# ]
realm = "Traefik2 Basic Auth"
usersFile = "/shared/.htpasswd" #be sure to mount the volume through docker-compose.yml
Aún es muy básico, aquí sólo le indicamos que Traefik usará un metodo de autenticación básico cuyas credenciales están en el fichero creado anteriormente /shared/.htpasswd.
2.5 Probando Traefik
Vale llegados a este punto ya vamos a poder ejecutar por primera vez el docker-compose.yml que si habéis ido bien y yo no la he liado... deberíais tenerlo de la siguiente manera:
Guardamos el fichero y lo ejecutamos
docker-compose -f docker-compose.yml up -d
Si todo va Ok deberíamos de poder poner en el navegador "traefik.example.com" y tras rellenar usuario y contraseña deberíamos ver:
En próximos posts pongo los siguientes pasos:
- Acabar configuración SSL
- Middleware de Seguridad
- Configurar Authelia
- Configurar Servicio QBittorrent
- Configurar Servicio Heimdall