Introducción

Dende que comecei co proxecto de servizos autoaloxados, todo funciona con HTTP. A fin de contas estou a correr todo na miña rede local. Pero comecei a pensar que en certas ocasións estaría ben ter a opción de acceder dende fora aos meus arquivos. A ver, o noventa e cinco por cento do tempo, é suficiente. Sen embargo, en ocasións teño que viaxar por traballo. Nestes casos cando estou no hotel, ou na oficina, podo precisar acceder a algún contido que teño na casa. Entón comecei a pensar en facer accesibles os servizos dende internet e claro, que vaian sen cifrar xa non me parece ben.
Sei que hai opcións, como montar unha VPN con Wireguard, utilizar Tailscale, etc. Isto faría que dende fora fosen cifrados, a todos aqueles que non estean conectados á VPN. Pero ter unha capa extra de seguridade como HTTPS non me parece mal. Ademais, tamén está a cuestión lúdica de probar un proxecto co que ter certificados autoasinados e autorenovables. Algo como Letsencrypt, pero local.
No principio pensei en Letsencrypt, pero require ter un dominio asociado e ao ser un proxecto local, non teño. De momento tamén descarto contratar un VPS.

Así que a través dun grupo de Telegram, publicaron un proxecto que facía o que precisaba. O usuario estribiyo creou un proxecto que aunaba Traefik con Step-ca. O proxecto ten outras cousas que a min non me interesan, tamén engade algunhas dependencias ou tecnoloxías, que non vexo necesarias no meu sistema. Dito isto, baseime neste proxecto para chegar a onde quería.

Step-ca

Smallstep son os creadores de Step-ca un proxecto que nos permite xerar os nosos propios certificados, de xeito local. Para isto podemos crear a nosa propia autoridade de certificación. Esta será a que asine os nosos certificados, polo que ao incluíla entre as entidades de confianza no sistema, ou no navegador, evitaremos os avisos de risco cando accedamos ás páxinas. Avisos de que estamos a recibir un certificado autoasinado.

Descartando Rust

Como podemos ver no proxecto de estribiyo, el utiliza un justfile o cal engade a dependencia de Rust. Quero poder desplegar os meus servizos de xeito sinxelo, cas mínimas dependencias posibles. Así que no meu caso creei un Makefile que fai o mesmo.
Así cando despregue na Raspberry non teño que instalar Rust, nin Cargo. E no caso de precisar formatear o despregue sería igual.

Si que hai un par de dependencias que mantiven como jq, pero son ferramentas que de cando en cando utilizo. Así que estas non as vexo mal.

Activando certificados locais

Step-ca: configuración

O primeiro foi activar un novo servizo en docker, o de Step-ca. Como é unha dependencia de Traefik, púxeno no mesmo docker-compose. Non é unha dependencia directa, ata hoxe non o tiña. Pero si que preciso que este servizo arranque antes que Traefik, para que este poida pedir os certificados. A configuración deste servizo é bastante pequena. As dores de cabeza están mais do lado de Traefik. Esta foi a configuración a engadir no docker-compose.

step-ca: # Service to generate certificates
    image: smallstep/step-ca:latest
    container_name: step-ca
    restart: on-failure
    user: root  # Run as root to avoid permission issues
    extra_hosts:
      - "${STEP_CA_URL}:127.0.0.1"
    ports:
      - "9000:9000"
    volumes:
      - ${STEP_DIR}:/home/step
    environment:
      - STEPPATH=/home/step
    networks:
      proxy:
        aliases:
          - ${STEP_CA_URL}

Con extra_hosts o que estou a facer é indicarlle onde debe resolver o dominio no que estará o servizo. Por exemplo raspberry.local, apuntará á IP de loopback. As outras variables son para montar a carpeta do host, enlazándoa co home do contenedor. De xeito que aí se xerarán os ficheiros de configuración e os certificados.

Traefik: configuración

Isto trouxo cambios na configuración de Traefik, xa que tiven que indicarlle que a partir de agora vai usar un servizo para xerar certificados. Pero imos ir de máis simple a máis complexo. Primeiro, o arquivo traefik.yml ca configuracion pasou a ser traefik.template.yml. Isto foi así porque requería ter algunhas variables de configuración para que collese o dominio e o email. Como non se cambian directamente faise mediante un script que lanzará envsubst xerando o arquivo final.

Este é o script que fai esa operación, ademáis de crear o ficheiro que almacenará os certificados e mover o da entidade certificadora a onde toca.

#!/bin/sh
set -e

# Replace environment variables in traefik.yml
envsubst < /etc/traefik/traefik.template.yml > /etc/traefik/traefik.yml

# Create acme.json if it doesn't exist
if [ ! -f /certs/acme.json ]; then
    touch /certs/acme.json
    chmod 600 /certs/acme.json
fi

# Copy CA certificate
if [ -f /certs/root_ca.crt ]; then
    cp /certs/root_ca.crt /usr/local/share/ca-certificates/
    update-ca-certificates
fi

# Start Traefik
exec "$@"

Esta é a configuración de traefik.

api:
  dashboard: true
  insecure: true

entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
          permanent: true
  https:
    address: ":443"

serversTransport:
    insecureSkipVerify: false
    rootCAs:
      - /certs/root_ca.crt

providers:
  docker:
    network: proxy
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  step:
    acme:
      email: ${STEP_CA_ADMIN_EMAIL}
      storage: /certs/acme.json
      caServer: https://${STEP_CA_DNS}${STEP_CA_ADDRESS}/acme/acme-provisioner/directory
      httpChallenge:
        entryPoint: http

Aquí quedan cousas por cambiar no futuro, por exemplo activar seguridade no dashboard. Agora mesmo calquera pode acceder, polo que a futuro activarei o login.

Outros detalles a destacar serían a redirección de HTTP a HTTPS de xeito permanente, co cal desactivamos a posibilidade de acceder de xeito non cifrado. Tamén vemos como establecemos a entidade certificadora que se xerará en Step-ca. E por último vemos a configuración do xerador de certificados. O valor de STEP_CA_DNS debe coíncidir co dominio que indiquemos no noso root_ca.crt ou darános erro por ser distintos e non se considerarán válidos os certificados.

Por último, nas labels de Traefik, no docker-compose.yml engadiremos as necesarias para utilizar Step-ca. Así é como quedan:

labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`${DOMAIN:-localhost}`)"
      - "traefik.http.routers.traefik.entrypoints=https"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=step"
      - "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
      - "traefik.http.routers.traefik.tls.domains[0].sans=www.${DOMAIN}"

Con respecto ao que tiña antes, mudei o entrypoint a https que coíncide co nome no traefik.template.yml anterior. Por iso vai en minúsculas. Activamos tls, indicando cal é o encargado de resolver os certificados. E por ultimo indicamos cal é o dominio principal.

Este sería o docker-compose.yaml compreto.

services:
  step-ca: # Service to generate certificates
    image: smallstep/step-ca:latest
    container_name: step-ca
    restart: on-failure
    user: root  # Run as root to avoid permission issues
    extra_hosts:
      - "${STEP_CA_URL}:127.0.0.1"
    ports:
      - "9000:9000"
    volumes:
      - ${STEP_DIR}:/home/step
    environment:
      - STEPPATH=/home/step
    networks:
      proxy:
        aliases:
          - ${STEP_CA_URL}

  traefik:
    build:
      context: .
      dockerfile: Dockerfile.traefik
    container_name: traefik
    init: true
    restart: on-failure
    depends_on:
      - step-ca
    networks:
      - proxy
    ports:
      - "80:80"
      - "8080:8080"
      - "443:443"
    extra_hosts:
      - "${DOMAIN}:${HOST_IP}"
    environment:
      - STEP_DIR=${STEP_DIR}
      - TZ=Europe/Madrid
      - STEP_CA_ADMIN_EMAIL=${STEP_CA_ADMIN_EMAIL}
      - STEP_CA_DNS=${STEP_CA_DNS}
      - STEP_CA_ADDRESS=${STEP_CA_ADDRESS}
    entrypoint: /custom-entrypoint.sh
    command: ["traefik", "--configFile=/etc/traefik/traefik.yml"]
    volumes:
      - $XDG_RUNTIME_DIR/docker.sock:/var/run/docker.sock:ro
      - ${STEP_DIR}:/certs
      - ./traefik.template.yml:/etc/traefik/traefik.template.yml
      - ./scripts/entrypoint.sh:/custom-entrypoint.sh
      - ${STEP_DIR}/certs/root_ca.crt:/usr/local/share/ca-certificates/root_ca.crt:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`${DOMAIN:-localhost}`)"
      - "traefik.http.routers.traefik.entrypoints=https"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=step"
      - "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN}"
      - "traefik.http.routers.traefik.tls.domains[0].sans=www.${DOMAIN}"

networks:
  proxy:
    name: proxy
    driver: bridge

Nos volumes podemos ver como apuntamos ao directorio de certificados, ao template que usaremos para a configuración de Traefik, ao script de entrypoint e ao certificado da entidade.

Configurando servizos

Para mudar servizos de xeito que utilicen o HTTPS, simplemente llo indicaremos nas etiquetas.

labels:
      - "traefik.enable=true"
      - "traefik.http.routers.calibre.entrypoints=https"
      - "traefik.http.routers.calibre.rule=Host(`${URL:-localhost}`)"
      - "traefik.http.services.calibre.loadbalancer.server.port=${CALIBRE_HTTP_PORT:-8083}"
      - "traefik.http.routers.calibre.tls.certresolver=step"

Ao igual que en traefik mudamos o entrypoint para utilizar https e engadimos o certresolver. O resto do ficheiro permanece igual. Noutros servizos a configuración é a mesma.

Jellyfin: configuración

No caso de Jellyfin, tiven que facer algún axuste a maiores. Por exemplo, cando a transimisión non vai securizada o porto que utiliza é o 8096, que o propio docker de Jellyfin xa ten exposto. Pero houben de engadir o 8920. A maiores engadin algunha variable de entorno a maiores, as seguintes:

services:
  jellyfin:
    # resto da configuración
    environment:
      - JELLYFIN_PublishedServerUrl=$URL
      - JELLYFIN_EnableHttps=true
      - JELLYFIN_HttpsPortNumber=${PORT}
      - JELLYFIN_EnableRemoteAccess=true

root_ca.crt

Con isto temos certificados autoasinados. Pero para que os poidamos utilizar quedan algúns pasos. Por exemplo, para que as webs non nos pregunten continuamente, deberemos engadir o certificado ao navegador. Mentres que outras aplicacións de escritorio, requerirán que se engada ao sistema. Isto último tamén é así en Android. Para poder sincronizar aplicacións cos servizos o sistema ten que recoñecer a nosa entidade coma unha entidade válida.

Se temos un sistema con entorno gráfico, xeralmente o abrir o root_ca.crt xa nos preguntará se o queremos instalar. No caso de facelo por terminal deberemos copialo no directorio adecuado e actualizar os certificados. A continuación reiniciar calquer aplicación que tivésemos aberta e que teña que utilizar o certificado.

Por exemplo, no caso de Raspberry Pi OS, copiei o certificado a /usr/local/share/ca-certificates/ e despois executei sudo update-ca-certificates. Isto foi preciso, para que Kodi aceptase os certificados autoasinados como válidos. En caso contrario mesmo se no addon de Jellycon inicaba que non se comprobasen, daba erro.

Este era o erro en cuestión

 error <general>: CCurlFile::Stat - <https://jellyfin.trast3b.local/Items/09bd6adadd4c56dce17a08ed16cae376/Images/Primary/0?Format=original&Tag=45d187c36e4dd2cd2dbf7484291322b1> Failed: SSL peer certificate or SSH remote key was not OK(60)

Conclusión

Agora teño os servizos que se transmiten pola rede local securizados, o que me permitirá facer probas e decidir como quero abrir os servizos no futuro. Deste xeito se accedo a través do móbil dende fora da casa, ou dende outra rede o datos non serán tan facilmente recoñecibles. Isto non quere dicir que vaia conectar dende unha Wifi descoñecida sen VPN, pero mesmo se o fago dende a oficina ou o que fose. A miña información estará máis protexida.

Fontes